From fba62c891f0ca6b153db3ea8eb8a195e464293a8 Mon Sep 17 00:00:00 2001 From: Eliav Maimon Date: Tue, 23 Jun 2026 17:35:56 +0300 Subject: [PATCH 1/8] feat: add dialog to delete raster layer --- src/common/actions/entity.actions.ts | 8 + src/common/i18n/he.json | 5 +- src/common/i18n/helpers/helpers.ts | 3 - .../layer-details/entity.delete-dialog.css | 14 +- .../layer-details/entity.delete-dialog.tsx | 58 ++-- .../layer-details/raster.delete-dialog.css | 121 +++++++++ .../layer-details/raster.delete-dialog.tsx | 251 ++++++++++++++++++ .../raster/layer-details-form.raster.css | 23 -- .../layer-details/raster/pp-map.css | 30 ++- .../layer-details/raster/pp-map.tsx | 6 +- src/discrete-layer/models/userStore.ts | 2 +- .../components/action-resolver.component.tsx | 22 +- .../views/discrete-layer-view.tsx | 15 ++ 13 files changed, 479 insertions(+), 79 deletions(-) delete mode 100644 src/common/i18n/helpers/helpers.ts create mode 100644 src/discrete-layer/components/layer-details/raster.delete-dialog.css create mode 100644 src/discrete-layer/components/layer-details/raster.delete-dialog.tsx diff --git a/src/common/actions/entity.actions.ts b/src/common/actions/entity.actions.ts index 835b36f58..8dc6ecbaa 100644 --- a/src/common/actions/entity.actions.ts +++ b/src/common/actions/entity.actions.ts @@ -115,6 +115,14 @@ const ACTIONS_CONFIG: IEntityActions[] = [ dependentField: { field: 'layerURLMissing', expectedValue: false }, views: [TabViews.CATALOG, TabViews.SEARCH_RESULTS], }, + { + action: 'delete', + frequent: false, + icon: '', + class: 'mc-icon-Delete', + titleTranslationId: 'action.delete.tooltip', + views: [TabViews.CATALOG, TabViews.SEARCH_RESULTS], + }, ], }, ], diff --git a/src/common/i18n/he.json b/src/common/i18n/he.json index cf55a4a4d..2fd38de59 100644 --- a/src/common/i18n/he.json +++ b/src/common/i18n/he.json @@ -53,7 +53,7 @@ "general.title.edit": "עריכת מידע של מוצר {value}", "general.title.update": "עדכון מוצר {value}", "general.title.locked": "בתהליך - נעול לעדכון", - "general.title.delete": "מחיקת מוצר {value}", + "general.title.Delete": "מחיקת מוצר {value}", "general.title.view": "צפייה במוצר {value}", "general.title.choose": "בחירת קבצים", "general.dialog.exit.title": "בטוח שברצונך להמשיך?", @@ -179,6 +179,9 @@ "delete.dialog.action": "מחיקת", "delete.dialog.checkbox": "אישור מחיקה", + "delete.approver-name": "שם מבצע:", + "delete.approver-code": "קוד מאשר:", + "snack.message.success": "הצלחה", "snack.message.failed": "כשלון", "snack.dismiss-btn.text": "אישור", diff --git a/src/common/i18n/helpers/helpers.ts b/src/common/i18n/helpers/helpers.ts deleted file mode 100644 index 6fb34a986..000000000 --- a/src/common/i18n/helpers/helpers.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const isRtl = (locale: string) => { - return locale === 'he'; -}; diff --git a/src/discrete-layer/components/layer-details/entity.delete-dialog.css b/src/discrete-layer/components/layer-details/entity.delete-dialog.css index 2504f12cb..53871d8a3 100644 --- a/src/discrete-layer/components/layer-details/entity.delete-dialog.css +++ b/src/discrete-layer/components/layer-details/entity.delete-dialog.css @@ -1,9 +1,11 @@ #entityDeleteDialog { - --update-layer-header-height: 150px; - --footer-height: 60px; - --footer-offset: 4px; - --modal-height: calc(100vh - 80px); + --header-height: 150px; + --warning-message-height: 60px; + --footer-height: 48px; + --footer-offset: 40px; + --modal-height: 100vh; --modal-width: 888px; + --map-height: calc(100% - var(--footer-offset) - var(--header-height) - var(--footer-height) - var(--warning-message-height) - var(--footer-offset)); } #entityDeleteDialog .icon { @@ -45,7 +47,7 @@ body[dir='rtl'] #entityDeleteDialog .closeIcon { } #entityDeleteDialog .headerWarning { - height: 100px; + height: var(--warning-message-height); display: flex; align-items: center; } @@ -70,7 +72,7 @@ body[dir='rtl'] #entityDeleteDialog .closeIcon { align-items: center; justify-content: center; width: 100%; - height: var(--update-layer-header-height); + height: var(--header-height); } #deleteLayerDetails { diff --git a/src/discrete-layer/components/layer-details/entity.delete-dialog.tsx b/src/discrete-layer/components/layer-details/entity.delete-dialog.tsx index 5526b68ee..b50ac1d50 100644 --- a/src/discrete-layer/components/layer-details/entity.delete-dialog.tsx +++ b/src/discrete-layer/components/layer-details/entity.delete-dialog.tsx @@ -34,10 +34,39 @@ interface EntityDeleteDialogProps { onSetOpen: (open: boolean) => void; recordType?: RecordType; layerRecord: ILayerImage; + // recordType?: RecordType; } const VALID = 'ok'; + +interface DeleteTitleProps { + domain: string; + action: Mode; + onClose: () => void; +} + +export const DialogsTitle: React.FC = (props) => { + const intl = useIntl(); + const title = intl.formatMessage( + { id: `general.title.${props.action}` }, + { value: props.domain } + ); + + return ( + + {title} + { + props.onClose(); + }} + /> + + ); +} + export const EntityDeleteDialog: React.FC = observer( (props: EntityDeleteDialogProps) => { const { isOpen, onSetOpen, layerRecord } = props; @@ -55,11 +84,6 @@ export const EntityDeleteDialog: React.FC = observer( id: `record-type.${(dialogTitleParam as string).toLowerCase()}.label`, }); - const dialogTitle = intl.formatMessage( - { id: `general.title.delete` }, - { value: dialogTitleParamTranslation } - ); - const closeDialog = useCallback(() => { onSetOpen(false); }, [onSetOpen, store.discreteLayersStore]); @@ -100,7 +124,7 @@ export const EntityDeleteDialog: React.FC = observer( ); }; - const deleteMessage = useMemo((): string => { + const warningMessage = useMemo((): string => { return intl.formatMessage( { id: 'delete.dialog.message' }, { action: emphasizeByHTML(`${intl.formatMessage({ id: 'delete.dialog.action' })}`) } @@ -108,26 +132,15 @@ export const EntityDeleteDialog: React.FC = observer( }, []); return ( -
+ - - {dialogTitle} - { - closeDialog(); - }} - /> - + - - @@ -147,11 +160,10 @@ export const EntityDeleteDialog: React.FC = observer( mode={Mode.VIEW} jsonValue={JSON.stringify(props.layerRecord?.footprint)} fitOptions={{ padding: [80, 160, 80, 160] }} - style={{ width: '100%', height: '480px' }} + style={{ width: '100%', height: 'var(--map-height)' }} /> - = observer( -
+ ); } ); diff --git a/src/discrete-layer/components/layer-details/raster.delete-dialog.css b/src/discrete-layer/components/layer-details/raster.delete-dialog.css new file mode 100644 index 000000000..431156428 --- /dev/null +++ b/src/discrete-layer/components/layer-details/raster.delete-dialog.css @@ -0,0 +1,121 @@ +#rasterDeleteDialog { + --header-height: 150px; + --warning-message-height: 60px; + /* --details-height: 150px; */ + --fields-height: 36px; + --fields-offset: 8px; + --footer-height: 80px; + --modal-height: 100vh; + --modal-width: 888px; + --map-height: calc(100% - var(--header-height) - var(--warning-message-height) - var(--fields-height) - var(--fields-offset) - var(--footer-height)); +} + +#rasterDeleteDialog .detailsPanelProductView { + height: var(--details-height); +} + +#rasterDeleteDialog .icon { + color: var(--mdc-theme-gc-warning-high); + font-size: 28px; + margin: 12px; +} + +#rasterDeleteDialog .footer { + display: flex; + width: 100%; + align-items: center; + height: var(--footer-height); +} + +body[dir='rtl'] #rasterDeleteDialog .footer { + left: 20px; +} + +#rasterDeleteDialog .buttons { + display: flex; + gap: 20px; + align-items: center; + margin-left: auto; +} + +body[dir='rtl'] #rasterDeleteDialog .buttons { + margin-left: unset; + margin-right: auto; +} + +#rasterDeleteDialog .closeIcon { + margin-left: auto; +} + +body[dir='rtl'] #rasterDeleteDialog .closeIcon { + margin-left: unset; + margin-right: auto; +} + +#rasterDeleteDialog .deleteWarning { + height: var(--warning-message-height); + display: flex; + align-items: center; +} + +@media (min-width: 900px) { + #rasterDeleteDialog .mdc-dialog .mdc-dialog__surface { + max-width: var(--modal-width); + height: var(--modal-height); + } + + #rasterDeleteDialog .mdc-dialog .mdc-dialog__surface .mdc-dialog .mdc-dialog__surface { + max-width: var(--modal-width); + height: var(--chonky-modal-height); + } +} + +#rasterDeleteDialog .deleteLayerDetailsContainer { + position: relative; + display: flex; + background-color: var(--mdc-theme-text-primary-on-light); + padding: 8px; + align-items: center; + justify-content: center; + width: 100%; + height: var(--header-height); +} + +#rasterDeleteDialog .deleteLayerDetails { + margin: 0 0; + width: 100%; + height: 100%; + overflow-y: auto; +} + +#rasterDeleteDialog .errors { + width: 100%; +} + +#rasterDeleteDialog .textCategoryField { + width: 45%; + margin: 0; + box-sizing: border-box; + white-space: nowrap; + text-overflow: ellipsis; +} + +#rasterDeleteDialog .fields { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + height: var(--fields-height); + gap: 16px; + margin-top: var(--fields-offset); +} + +#rasterDeleteDialog .field { + display: flex; + flex-direction: row; + width: 100%; + padding: 4px 4px; +} + +#rasterDeleteDialog .field .mdc-text-field { + flex: 1; +} diff --git a/src/discrete-layer/components/layer-details/raster.delete-dialog.tsx b/src/discrete-layer/components/layer-details/raster.delete-dialog.tsx new file mode 100644 index 000000000..d40712fd5 --- /dev/null +++ b/src/discrete-layer/components/layer-details/raster.delete-dialog.tsx @@ -0,0 +1,251 @@ +import React, { useCallback, useState, useMemo, useEffect, useRef } from 'react'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { observer } from 'mobx-react'; +import { + Button, + Checkbox, + CircularProgress, + DialogActions, + DialogContent, + TextField, + Tooltip, +} from '@map-colonies/react-core'; +import { Dialog, DialogTitle, Icon, IconButton, Typography } from '@map-colonies/react-core'; +import { Box } from '@map-colonies/react-components'; +import { GraphQLError } from '../../../common/components/error/graphql.error-presentor'; +import { emphasizeByHTML } from '../../../common/helpers/formatters'; +import { Mode } from '../../../common/models/mode.enum'; +import { ILayerImage } from '../../models/layerImage'; +import { IDispatchAction } from '../../models/actionDispatcherStore'; +import { UserAction } from '../../models/userStore'; +import { + EntityDescriptorModelType, + RecordStatus, + RecordType, + useQuery, + useStore, +} from '../../models'; +import { LayersDetailsComponent } from './layer-details'; + +import './raster.delete-dialog.css'; +import { DialogsTitle } from './entity.delete-dialog'; +import { GeoFeaturesPresentorComponent } from './raster/pp-map'; +import { Formik, FormikProps } from 'formik'; +import { FieldLabelComponent } from '../../../common/components/form/field-label'; + +interface EntityDeleteDialogProps { + isOpen: boolean; + onSetOpen: (open: boolean) => void; + recordType?: RecordType; + layerRecord: ILayerImage; + // recordType?: RecordType; +} + +const VALID = 'ok'; + +export const RasterDeleteDialog: React.FC = observer( + (props: EntityDeleteDialogProps) => { + const { isOpen, onSetOpen, layerRecord } = props; + const store = useStore(); + const mutationQuery = useQuery(); + const intl = useIntl(); + const [allowDeleting, setAllowDeleting] = useState(false); + + const [recordType] = useState( + props.recordType ?? (layerRecord?.type as RecordType) + ); + + const dialogTitleParam = recordType; + const dialogTitleParamTranslation = intl.formatMessage({ + id: `record-type.${(dialogTitleParam as string).toLowerCase()}.label`, + }); + + const closeDialog = useCallback(() => { + onSetOpen(false); + }, [onSetOpen, store.discreteLayersStore]); + + const dispatchAction = (action: Record): void => { + store.actionDispatcherStore.dispatchAction({ + action: action.action, + data: action.data, + } as IDispatchAction); + }; + + useEffect(() => { + if ( + !mutationQuery.loading && + (mutationQuery.data as { deleteLayer: string } | undefined)?.deleteLayer === VALID + ) { + onSetOpen(false); + const payload = { + action: UserAction.SYSTEM_CALLBACK_DELETE, + data: { + ...layerRecord, + productStatus: RecordStatus.BEING_DELETED, + }, + }; + + dispatchAction(payload); + } + }, [mutationQuery.data]); + + const deleteLayer = (): void => { + mutationQuery.setQuery( + store.mutateDeleteLayer({ + data: { + id: layerRecord.id, + type: layerRecord.type as RecordType, + }, + }) + ); + }; + + const warningMessage = useMemo((): string => { + return intl.formatMessage( + { id: 'delete.dialog.message' }, + { action: emphasizeByHTML(`${intl.formatMessage({ id: 'delete.dialog.action' })}`) } + ); + }, []); + + const formRef = useRef(null); + + let formikRef = useRef>() as any; + + useEffect(() => { + console.log('formikRef.isVaild', formikRef.isVaild) + }, [formikRef.isVaild]) + + + const [filterValues, setFilterValues] = React.useState({ approverCode: '', approverName: '' }); + + return ( + + + + + + + + + + + + + + + + { + if (instance) { + formikRef.current = instance; + } + }} + validateOnMount + validate={(values) => { + const errors: any = {}; + + if (!values.approverName?.trim()) { + errors.approverName = 'Required'; + } + + if (!values.approverCode?.trim()) { + errors.approverCode = 'Required'; + } + + return errors; + }} + onSubmit={(values, actions) => { + setAllowDeleting(formRef.current?.checkValidity() ?? false); + deleteLayer(); + }} + > + {(props) => ( +
+ + + + + + + + + + + + + + + + + + + +
+ )} +
+
+
+
+ ); + } +); diff --git a/src/discrete-layer/components/layer-details/raster/layer-details-form.raster.css b/src/discrete-layer/components/layer-details/raster/layer-details-form.raster.css index 5023721ba..61561413c 100644 --- a/src/discrete-layer/components/layer-details/raster/layer-details-form.raster.css +++ b/src/discrete-layer/components/layer-details/raster/layer-details-form.raster.css @@ -116,29 +116,6 @@ body[dir='rtl'] #layerDetailsFormRaster .enumOptions .mdc-select__dropdown-icon direction: inherit !important; } -#layerDetailsFormRaster .mdc-checkbox__ripple { - display: none; -} - -#layerDetailsFormRaster .flexCheckItem { - flex: 1; - position: relative; -} - -#layerDetailsFormRaster .mdc-checkbox__background { - width: 12px; - height: 12px; -} - -#layerDetailsFormRaster .showOnMapContainer .mdc-checkbox { - padding-left: 0px; -} - -#layerDetailsFormRaster .showOnMapContainer label { - padding-bottom: 6px; - color: var(--mdc-theme-text-icon-on-dark); -} - #layerDetailsFormRaster .remainingTime { width: auto; position: absolute; diff --git a/src/discrete-layer/components/layer-details/raster/pp-map.css b/src/discrete-layer/components/layer-details/raster/pp-map.css index 8b6bb6dd0..a5586a572 100644 --- a/src/discrete-layer/components/layer-details/raster/pp-map.css +++ b/src/discrete-layer/components/layer-details/raster/pp-map.css @@ -1,4 +1,4 @@ -#entityRasterDialog .geoFeaturesMapContainer .checkbox { +#geoFeaturesMapContainer .checkbox { display: inline-block; position: absolute; bottom: 1px; @@ -9,22 +9,40 @@ padding-right: 12px; } -body[dir='rtl'] #entityRasterDialog .geoFeaturesMapContainer .checkbox { +body[dir='rtl'] #geoFeaturesMapContainer .checkbox { direction: rtl; padding-right: unset; padding-left: 12px; } -.geoFeaturesMapContainer { +#geoFeaturesMapContainer { position: relative; } -.geoFeaturesMapContainer .ol-zoom button, -.geoFeaturesMapContainer .ol-legend button { +#geoFeaturesMapContainer .ol-zoom button, +#geoFeaturesMapContainer .ol-legend button { cursor: pointer; } -.featurePropertiesPopup { +#geoFeaturesMapContainer .mdc-checkbox__ripple { + display: none; +} + +#geoFeaturesMapContainer .showOnMapContainer .mdc-checkbox { + padding-left: 0px; +} + +#geoFeaturesMapContainer .mdc-checkbox__background { + width: 12px; + height: 12px; +} + +#geoFeaturesMapContainer .showOnMapContainer label { + padding-bottom: 6px; + color: var(--mdc-theme-text-icon-on-dark); +} + +#geoFeaturesMapContainer .featurePropertiesPopup { position: absolute; top: 8px; left: 8px; diff --git a/src/discrete-layer/components/layer-details/raster/pp-map.tsx b/src/discrete-layer/components/layer-details/raster/pp-map.tsx index ccaa3471c..98fa3cd7f 100644 --- a/src/discrete-layer/components/layer-details/raster/pp-map.tsx +++ b/src/discrete-layer/components/layer-details/raster/pp-map.tsx @@ -262,7 +262,7 @@ export const GeoFeaturesPresentorComponent: React.FC }; return ( - + {previewBaseMap} @@ -286,10 +286,10 @@ export const GeoFeaturesPresentorComponent: React.FC /> - {mode === Mode.UPDATE && ( + {mode !== Mode.NEW && ( ): void => { diff --git a/src/discrete-layer/models/userStore.ts b/src/discrete-layer/models/userStore.ts index 96359060e..222e32bca 100644 --- a/src/discrete-layer/models/userStore.ts +++ b/src/discrete-layer/models/userStore.ts @@ -116,7 +116,7 @@ const ROLES: IRole[] = [ [UserAction.ENTITY_ACTION_VECTORBESTRECORD_EDIT]: {enabled: false}, [UserAction.ENTITY_ACTION_QUANTIZEDMESHBESTRECORD_EDIT]: {enabled: true}, [UserAction.ENTITY_ACTION_LAYERRASTERRECORD_UPDATE]: {enabled: true}, - [UserAction.ENTITY_ACTION_LAYERRASTERRECORD_DELETE]: {enabled: false}, + [UserAction.ENTITY_ACTION_LAYERRASTERRECORD_DELETE]: {enabled: true}, [UserAction.ENTITY_ACTION_LAYER3DRECORD_DELETE]: {enabled: true}, [UserAction.ENTITY_ACTION_LAYERDEMRECORD_DELETE]: {enabled: false}, [UserAction.ENTITY_ACTION_LAYERRASTERRECORD_PUBLISH]: {enabled: true}, diff --git a/src/discrete-layer/views/components/action-resolver.component.tsx b/src/discrete-layer/views/components/action-resolver.component.tsx index d0aabf932..8d8842d66 100644 --- a/src/discrete-layer/views/components/action-resolver.component.tsx +++ b/src/discrete-layer/views/components/action-resolver.component.tsx @@ -206,8 +206,7 @@ export const ActionResolver: React.FC = observer((props) => break; case 'LayerRasterRecord.edit': store.discreteLayersStore.selectLayer( - // @ts-ignore - cleanUpEntity(data, LayerRasterRecordModelKeys) as LayerMetadataMixedUnion + cleanUpEntity(data, LayerRasterRecordModelKeys) as unknown as LayerMetadataMixedUnion ); store.discreteLayersStore.setSelectedLayerOperationMode(Mode.EDIT); handleOpenEntityDialog(RecordType.RECORD_RASTER, true); @@ -288,15 +287,16 @@ export const ActionResolver: React.FC = observer((props) => handleFlyTo(); break; case 'LayerRasterRecord.update': - store.discreteLayersStore.selectLayer( - // @ts-ignore - cleanUpEntity(data, LayerRasterRecordModelKeys) as LayerMetadataMixedUnion, - // @ts-ignore - true - ); + store.discreteLayersStore.selectLayer(cleanUpEntity(data, LayerRasterRecordModelKeys) as unknown as LayerMetadataMixedUnion); store.discreteLayersStore.setSelectedLayerOperationMode(Mode.UPDATE); handleOpenEntityDialog(RecordType.RECORD_RASTER, true); break; + case 'LayerRasterRecord.delete': + store.discreteLayersStore.selectLayer( + cleanUpEntity(data, LayerRasterRecordModelKeys) as unknown as LayerMetadataMixedUnion); + store.discreteLayersStore.setSelectedLayerOperationMode(Mode.DELETE); + handleOpenEntityDialog(RecordType.RECORD_RASTER, true); + break; case 'Layer3DRecord.viewer': window.open( `${CONFIG.WEB_TOOLS_URL}/${CONFIG.MODEL_VIEWER_ROUTE}?model_ids=${data.productId}&token=${CONFIG.MODEL_VIEWER_TOKEN_VALUE}` @@ -304,11 +304,7 @@ export const ActionResolver: React.FC = observer((props) => break; case 'Layer3DRecord.delete': store.discreteLayersStore.selectLayer( - // @ts-ignore - cleanUpEntity(data, Layer3DRecordModelKeys) as LayerMetadataMixedUnion, - // @ts-ignore - false, - true + cleanUpEntity(data, Layer3DRecordModelKeys) as unknown as LayerMetadataMixedUnion ); store.discreteLayersStore.setSelectedLayerOperationMode(Mode.DELETE); handleOpenEntityDialog(RecordType.RECORD_3D, true); diff --git a/src/discrete-layer/views/discrete-layer-view.tsx b/src/discrete-layer/views/discrete-layer-view.tsx index 6665ad21f..1a62b9ac6 100644 --- a/src/discrete-layer/views/discrete-layer-view.tsx +++ b/src/discrete-layer/views/discrete-layer-view.tsx @@ -80,6 +80,7 @@ import { ILayerImage } from '../models/layerImage'; import { useStore } from '../models/RootStore'; import { FilterField } from '../models/RootStore.base'; import { UserAction, UserRole } from '../models/userStore'; +import { RasterDeleteDialog } from '../components/layer-details/raster.delete-dialog'; import { ActionResolver } from './components/action-resolver.component'; import AppTitle from './components/app-title/app-title.component'; import { DetailsPanel } from './components/details-panel.component'; @@ -142,6 +143,7 @@ const DiscreteLayerView: React.FC = observer(() => { const [isDemIngestDialogOpen, setIsDemIngestDialogOpen] = useState(false); const [isEntityDialogOpen, setIsEntityDialogOpen] = useState(false); const [isEntityDeleteDialogOpen, setIsEntityDeleteDialogOpen] = useState(false); + const [isRasterDeleteDialogOpen, setIsRasterDeleteDialogOpen] = useState(false); const [isSystemsJobsDialogOpen, setIsSystemsJobsDialogOpen] = useState(false); const [isSystemCoreInfoDialogOpen, setIsSystemCoreInfoDialogOpen] = useState(false); const [isCreateEntityMenuOpen, setIsCreateEntityMenuOpen] = useState(false); @@ -491,6 +493,7 @@ const DiscreteLayerView: React.FC = observer(() => { [Mode.UPDATE]: setIsRasterDialogOpen, [Mode.EDIT]: setIsEntityDialogOpen, [Mode.VIEW]: setIsEntityDialogOpen, + [Mode.DELETE]: setIsRasterDeleteDialogOpen, }, [RecordType.RECORD_3D]: { [Mode.NEW]: setIs3DIngestDialogOpen, @@ -1459,6 +1462,18 @@ const DiscreteLayerView: React.FC = observer(() => { layerRecord={store.discreteLayersStore.selectedLayer} /> )} + {permissions.isDeleteAllowed && + store.discreteLayersStore.selectedLayer && + isRasterDeleteDialogOpen && ( + { + setIsRasterDeleteDialogOpen(open); + onCloseDialog(); + }} + layerRecord={store.discreteLayersStore.selectedLayer} + /> + )} {isSystemsJobsDialogOpen && ( Date: Wed, 24 Jun 2026 16:49:06 +0300 Subject: [PATCH 2/8] fix: disable delete button, prettier --- src/common/actions/entity.actions.ts | 1 + .../actions.button-renderer.css | 5 + .../actions.button-renderer.tsx | 23 +++- .../components/catalog-tree/catalog-tree.tsx | 42 ++++++- .../delete-dialog/delete.hook.ts | 56 +++++++++ .../layer-details/entity.delete-dialog.css | 5 +- .../layer-details/entity.delete-dialog.tsx | 55 +++------ .../layer-details/raster.delete-dialog.css | 5 +- .../layer-details/raster.delete-dialog.tsx | 113 ++++++------------ .../models/actionDispatcherStore.ts | 6 +- .../components/action-resolver.component.tsx | 7 +- 11 files changed, 181 insertions(+), 137 deletions(-) create mode 100644 src/discrete-layer/components/layer-details/delete-dialog/delete.hook.ts diff --git a/src/common/actions/entity.actions.ts b/src/common/actions/entity.actions.ts index 8dc6ecbaa..eddbc07ca 100644 --- a/src/common/actions/entity.actions.ts +++ b/src/common/actions/entity.actions.ts @@ -15,6 +15,7 @@ export interface IAction { class: string; titleTranslationId: string; views: TabViews[]; + disabled?: boolean; dependentField?: DependentField; } diff --git a/src/common/components/tree/icon-renderers/actions.button-renderer.css b/src/common/components/tree/icon-renderers/actions.button-renderer.css index d019c1808..1cd0f37e3 100644 --- a/src/common/components/tree/icon-renderers/actions.button-renderer.css +++ b/src/common/components/tree/icon-renderers/actions.button-renderer.css @@ -65,3 +65,8 @@ body[dir='rtl'] .actionMenuItemTitle { padding-left: unset; padding-right: 8px; } + +.actionsContainer .disabled { + opacity: 0.5; + pointer-events: none; +} diff --git a/src/common/components/tree/icon-renderers/actions.button-renderer.tsx b/src/common/components/tree/icon-renderers/actions.button-renderer.tsx index 8f2ceb617..3e9c3cd79 100644 --- a/src/common/components/tree/icon-renderers/actions.button-renderer.tsx +++ b/src/common/components/tree/icon-renderers/actions.button-renderer.tsx @@ -52,6 +52,7 @@ export const ActionsRenderer: React.FC = ({ : `actionIcon actionDismissible` } icon={action.icon} + disabled={action.disabled} key={`freqAct_${node.id as string}_${idx}`} onClick={(): void => { sendAction(entity, action, node); @@ -72,20 +73,30 @@ export const ActionsRenderer: React.FC = ({ { + if (action.disabled) { + evt.preventDefault(); + evt.stopPropagation(); + return; + } sendAction(entity, action, node); setOpenActionsMenu(false); }} className="actionMenuItem" > - + {action.titleTranslationId} diff --git a/src/discrete-layer/components/catalog-tree/catalog-tree.tsx b/src/discrete-layer/components/catalog-tree/catalog-tree.tsx index 64a3afd1d..b6b3bc438 100644 --- a/src/discrete-layer/components/catalog-tree/catalog-tree.tsx +++ b/src/discrete-layer/components/catalog-tree/catalog-tree.tsx @@ -7,7 +7,7 @@ import { changeNodeAtPath, getNodeAtPath, find, ExtendedNodeData } from 'react-s import { useIntl } from 'react-intl'; import { Box } from '@map-colonies/react-components'; import { useTheme } from '@map-colonies/react-core'; -import { IActionGroup } from '../../../common/actions/entity.actions'; +import { IAction, IActionGroup } from '../../../common/actions/entity.actions'; import { TreeComponent, TreeItem } from '../../../common/components/tree'; import { ActionsRenderer } from '../../../common/components/tree/icon-renderers/actions.button-renderer'; import { FootprintRenderer } from '../../../common/components/tree/icon-renderers/footprint.icon-renderer'; @@ -15,7 +15,7 @@ import { LayerImageRenderer } from '../../../common/components/tree/icon-rendere import { ProductTypeRenderer } from '../../../common/components/tree/icon-renderers/product-type.icon-renderer'; import { Error } from '../../../common/components/tree/statuses/error'; import { Loading } from '../../../common/components/tree/statuses/loading'; -import { getTextStyle } from '../../../common/helpers/style'; +import { getTextStyle, isUnpublished } from '../../../common/helpers/style'; import { isValidLayerMetadata } from '../../../common/helpers/layer-url'; import { LinkType } from '../../../common/models/link-type.enum'; import { @@ -215,6 +215,33 @@ export const CatalogTreeComponent: React.FC = observe ); } + const disableActionByPredicate = ( + data: Record, + actionToDiable: string, + predicate: (data: Record) => boolean + ): IActionGroup[] => { + const actionGroups = (entityPermittedActions[data.__typename] as IActionGroup[]).map( + (group) => ({ + ...group, + group: group.group.map((action) => ({ ...action })), + }) + ); + + if (predicate(data)) { + const deleteGroup = actionGroups.find((group) => + group.group.some((action) => action.action === actionToDiable) + ); + + const deleteAction = deleteGroup?.group.find((action) => action.action === actionToDiable); + + if (deleteAction) { + deleteAction.disabled = true; + } + } + + return actionGroups; + }; + return ( <> {loading && } @@ -310,9 +337,14 @@ export const CatalogTreeComponent: React.FC = observe hoveredNode.parentPath === rowInfo.path.slice(0, -1).toString() && ( !isUnpublished(data) + )} entity={rowInfo.node.__typename} actionHandler={dispatchAction} /> diff --git a/src/discrete-layer/components/layer-details/delete-dialog/delete.hook.ts b/src/discrete-layer/components/layer-details/delete-dialog/delete.hook.ts new file mode 100644 index 000000000..82ee67321 --- /dev/null +++ b/src/discrete-layer/components/layer-details/delete-dialog/delete.hook.ts @@ -0,0 +1,56 @@ +import { useCallback, useMemo, useState } from 'react'; +import { useIntl } from 'react-intl'; +import { emphasizeByHTML } from '../../../../common/helpers/formatters'; +import { ILayerImage } from '../../../models/layerImage'; +import { IDispatchAction } from '../../../models/actionDispatcherStore'; +import { RecordType, useStore } from '../../../models'; + +export const VALID = 'ok'; + +interface DeleteLayerDialogOptions { + onSetOpen: (open: boolean) => void; + layerRecord: ILayerImage; + recordType?: RecordType; +} + +export const useDeleteLayerDialog = ({ + onSetOpen, + layerRecord, + recordType: recordTypeProp, +}: DeleteLayerDialogOptions) => { + const store = useStore(); + + const intl = useIntl(); + + const [recordType] = useState(recordTypeProp ?? (layerRecord?.type as RecordType)); + + const dialogTitleParamTranslation = intl.formatMessage({ + id: `record-type.${(recordType as string).toLowerCase()}.label`, + }); + + const closeDialog = useCallback(() => { + onSetOpen(false); + }, [onSetOpen, store.discreteLayersStore]); + + const dispatchAction = (action: Record): void => { + store.actionDispatcherStore.dispatchAction({ + action: action.action, + data: action.data, + } as IDispatchAction); + }; + + const warningMessage = useMemo((): string => { + return intl.formatMessage( + { id: 'delete.dialog.message' }, + { action: emphasizeByHTML(`${intl.formatMessage({ id: 'delete.dialog.action' })}`) } + ); + }, []); + + return { + recordType, + dialogTitleParamTranslation, + closeDialog, + dispatchAction, + warningMessage, + }; +}; diff --git a/src/discrete-layer/components/layer-details/entity.delete-dialog.css b/src/discrete-layer/components/layer-details/entity.delete-dialog.css index 53871d8a3..acbec5c92 100644 --- a/src/discrete-layer/components/layer-details/entity.delete-dialog.css +++ b/src/discrete-layer/components/layer-details/entity.delete-dialog.css @@ -5,7 +5,10 @@ --footer-offset: 40px; --modal-height: 100vh; --modal-width: 888px; - --map-height: calc(100% - var(--footer-offset) - var(--header-height) - var(--footer-height) - var(--warning-message-height) - var(--footer-offset)); + --map-height: calc( + 100% - var(--footer-offset) - var(--header-height) - var(--footer-height) - + var(--warning-message-height) - var(--footer-offset) + ); } #entityDeleteDialog .icon { diff --git a/src/discrete-layer/components/layer-details/entity.delete-dialog.tsx b/src/discrete-layer/components/layer-details/entity.delete-dialog.tsx index b50ac1d50..b60a24ed0 100644 --- a/src/discrete-layer/components/layer-details/entity.delete-dialog.tsx +++ b/src/discrete-layer/components/layer-details/entity.delete-dialog.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState, useMemo, useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import { observer } from 'mobx-react'; import { @@ -7,16 +7,12 @@ import { CircularProgress, DialogActions, DialogContent, - Tooltip, } from '@map-colonies/react-core'; import { Dialog, DialogTitle, Icon, IconButton, Typography } from '@map-colonies/react-core'; import { Box } from '@map-colonies/react-components'; import { GraphQLError } from '../../../common/components/error/graphql.error-presentor'; -import { emphasizeByHTML } from '../../../common/helpers/formatters'; import { Mode } from '../../../common/models/mode.enum'; import { ILayerImage } from '../../models/layerImage'; -import { IDispatchAction } from '../../models/actionDispatcherStore'; -import { UserAction } from '../../models/userStore'; import { EntityDescriptorModelType, RecordStatus, @@ -26,10 +22,12 @@ import { } from '../../models'; import { GeoJsonMapValuePresentorComponent } from './field-value-presentors/geojson-map.value-presentor'; import { LayersDetailsComponent } from './layer-details'; +import { useDeleteLayerDialog, VALID } from './delete-dialog/delete.hook'; import './entity.delete-dialog.css'; +import { UserAction } from '../../models/userStore'; -interface EntityDeleteDialogProps { +export interface EntityDeleteDialogProps { isOpen: boolean; onSetOpen: (open: boolean) => void; recordType?: RecordType; @@ -37,10 +35,7 @@ interface EntityDeleteDialogProps { // recordType?: RecordType; } -const VALID = 'ok'; - - -interface DeleteTitleProps { +export interface DeleteTitleProps { domain: string; action: Mode; onClose: () => void; @@ -65,35 +60,18 @@ export const DialogsTitle: React.FC = (props) => { /> ); -} +}; export const EntityDeleteDialog: React.FC = observer( (props: EntityDeleteDialogProps) => { const { isOpen, onSetOpen, layerRecord } = props; const store = useStore(); - const mutationQuery = useQuery(); const intl = useIntl(); + const mutationQuery = useQuery(); const [allowDeleting, setAllowDeleting] = useState(false); - const [recordType] = useState( - props.recordType ?? (layerRecord?.type as RecordType) - ); - - const dialogTitleParam = recordType; - const dialogTitleParamTranslation = intl.formatMessage({ - id: `record-type.${(dialogTitleParam as string).toLowerCase()}.label`, - }); - - const closeDialog = useCallback(() => { - onSetOpen(false); - }, [onSetOpen, store.discreteLayersStore]); - - const dispatchAction = (action: Record): void => { - store.actionDispatcherStore.dispatchAction({ - action: action.action, - data: action.data, - } as IDispatchAction); - }; + const { dialogTitleParamTranslation, closeDialog, dispatchAction, warningMessage } = + useDeleteLayerDialog({ onSetOpen, layerRecord, recordType: props.recordType }); useEffect(() => { if ( @@ -124,20 +102,17 @@ export const EntityDeleteDialog: React.FC = observer( ); }; - const warningMessage = useMemo((): string => { - return intl.formatMessage( - { id: 'delete.dialog.message' }, - { action: emphasizeByHTML(`${intl.formatMessage({ id: 'delete.dialog.action' })}`) } - ); - }, []); - return ( - + - + void; - recordType?: RecordType; - layerRecord: ILayerImage; - // recordType?: RecordType; -} - -const VALID = 'ok'; +import { UserAction } from '../../models/userStore'; export const RasterDeleteDialog: React.FC = observer( (props: EntityDeleteDialogProps) => { const { isOpen, onSetOpen, layerRecord } = props; const store = useStore(); - const mutationQuery = useQuery(); const intl = useIntl(); + const mutationQuery = useQuery(); const [allowDeleting, setAllowDeleting] = useState(false); - const [recordType] = useState( - props.recordType ?? (layerRecord?.type as RecordType) - ); - - const dialogTitleParam = recordType; - const dialogTitleParamTranslation = intl.formatMessage({ - id: `record-type.${(dialogTitleParam as string).toLowerCase()}.label`, - }); - - const closeDialog = useCallback(() => { - onSetOpen(false); - }, [onSetOpen, store.discreteLayersStore]); - - const dispatchAction = (action: Record): void => { - store.actionDispatcherStore.dispatchAction({ - action: action.action, - data: action.data, - } as IDispatchAction); - }; + const { + dialogTitleParamTranslation, + closeDialog, + dispatchAction, + warningMessage, + } = useDeleteLayerDialog({ onSetOpen, layerRecord, recordType: props.recordType }); useEffect(() => { if ( @@ -79,10 +46,7 @@ export const RasterDeleteDialog: React.FC = observer( onSetOpen(false); const payload = { action: UserAction.SYSTEM_CALLBACK_DELETE, - data: { - ...layerRecord, - productStatus: RecordStatus.BEING_DELETED, - }, + data: { ...layerRecord }, }; dispatchAction(payload); @@ -100,28 +64,18 @@ export const RasterDeleteDialog: React.FC = observer( ); }; - const warningMessage = useMemo((): string => { - return intl.formatMessage( - { id: 'delete.dialog.message' }, - { action: emphasizeByHTML(`${intl.formatMessage({ id: 'delete.dialog.action' })}`) } - ); - }, []); - - const formRef = useRef(null); - let formikRef = useRef>() as any; - useEffect(() => { - console.log('formikRef.isVaild', formikRef.isVaild) - }, [formikRef.isVaild]) - - - const [filterValues, setFilterValues] = React.useState({ approverCode: '', approverName: '' }); + const [initialDeleteValues, _] = React.useState({ approverCode: '', approverName: '' }); return ( - + @@ -151,7 +105,7 @@ export const RasterDeleteDialog: React.FC = observer( /> { if (instance) { @@ -173,14 +127,15 @@ export const RasterDeleteDialog: React.FC = observer( return errors; }} onSubmit={(values, actions) => { - setAllowDeleting(formRef.current?.checkValidity() ?? false); - deleteLayer(); + if (formikRef.current?.isValid) { + deleteLayer(); + } }} > {(props) => (
- - + + = observer( isRequired={true} /> - + = observer( isRequired={true} /> @@ -217,7 +172,7 @@ export const RasterDeleteDialog: React.FC = observer( - +