diff --git a/src/management-system-v2/app/(dashboard)/processes/page.tsx b/src/management-system-v2/app/(dashboard)/processes/page.tsx index 25a362d23..e2d171d8f 100644 --- a/src/management-system-v2/app/(dashboard)/processes/page.tsx +++ b/src/management-system-v2/app/(dashboard)/processes/page.tsx @@ -4,6 +4,11 @@ import { Result, Space } from 'antd'; import NotLoggedInFallback from './not-logged-in-fallback'; import { getProcesses } from '@/lib/data/legacy/process'; import Auth, { getCurrentUser } from '@/components/auth'; +// This is a workaround to enable the Server Actions in that file to return any +// client components. This is not possible with the current nextjs compiler +// otherwise. It might be possible in the future with turbopack without this +// import. +import '@/lib/data/processes'; const ProcessesPage = async () => { const { ability } = await getCurrentUser(); diff --git a/src/management-system-v2/components/ProcessSider.tsx b/src/management-system-v2/components/ProcessSider.tsx index 73eae1051..570900c6e 100644 --- a/src/management-system-v2/components/ProcessSider.tsx +++ b/src/management-system-v2/components/ProcessSider.tsx @@ -5,6 +5,7 @@ import { Menu } from 'antd'; const { SubMenu, Item, ItemGroup } = Menu; import { EditOutlined, + FileOutlined, ProfileOutlined, FileAddOutlined, StarOutlined, @@ -24,62 +25,79 @@ const ProcessSider: FC = () => { <> {ability.can('view', 'Process') ? ( - { - router.push(`/processes`); - }} - > - Process List - - } - className={activeSegment === 'processes' ? 'SelectedSegment' : ''} - icon={ - { - router.push(`/processes`); - }} - /> - } + } + hidden={!ability.can('view', 'Process')} > - } - hidden={!ability.can('create', 'Process')} + { + router.push(`/processes`); + }} > - New Process} - > - - } - hidden={!ability.can('create', 'Process')} - > - - - }> - Favorites - - - ) : null} + Process List + + + ) : // { + // router.push(`/processes`); + // }} + // > + // Process List + // + // } + // className={activeSegment === 'processes' ? 'SelectedSegment' : ''} + // icon={ + // { + // router.push(`/processes`); + // }} + // /> + // } + // > + // } + // hidden={!ability.can('create', 'Process')} + // > + // New Process} + // > + // + // } + // hidden={!ability.can('create', 'Process')} + // > + // + // + // }> + // Favorites + // + // + null} {ability.can('view', 'Template') ? ( - }> - } - hidden={!ability.can('create', 'Template')} - > - New Template - - }> - Favorites - - - ) : null} + }> + Templates + + ) : // }> + // } + // hidden={!ability.can('create', 'Template')} + // > + // New Template + // + // }> + // Favorites + // + // + null} ); diff --git a/src/management-system-v2/components/confirmation-button.tsx b/src/management-system-v2/components/confirmation-button.tsx index 5a1247b63..1f3eb18c0 100644 --- a/src/management-system-v2/components/confirmation-button.tsx +++ b/src/management-system-v2/components/confirmation-button.tsx @@ -12,6 +12,8 @@ type ConfirmationModalProps = { >; buttonProps?: ComponentProps; tooltip?: string; + externalOpen?: boolean; + onExternalClose?: () => void; }; const ConfirmationButton: FC> = ({ @@ -23,12 +25,15 @@ const ConfirmationButton: FC> = ({ modalProps, buttonProps, tooltip, + externalOpen, + onExternalClose, }) => { const [modalOpen, setModalOpen] = useState(false); const [loading, setLoading] = useState(false); const clearModal = () => { setModalOpen(false); + onExternalClose?.(); setLoading(false); }; @@ -48,10 +53,12 @@ const ConfirmationButton: FC> = ({ closeIcon={null} {...modalProps} title={title} - open={modalOpen} + open={externalOpen || modalOpen} onOk={onConfirmWrapper} confirmLoading={loading} - onCancel={() => (canCloseWhileLoading || !loading) && setModalOpen(false)} + onCancel={() => + ((canCloseWhileLoading || !loading) && setModalOpen(false)) || onExternalClose?.() + } cancelButtonProps={{ disabled: !canCloseWhileLoading && loading }} >

{description}

diff --git a/src/management-system-v2/components/content.module.scss b/src/management-system-v2/components/content.module.scss index 132e0377b..d45d2e74b 100644 --- a/src/management-system-v2/components/content.module.scss +++ b/src/management-system-v2/components/content.module.scss @@ -29,4 +29,28 @@ padding: 0; } } + + .Icon { + margin: 15px -10px; + } + + .LogoContainer { + display: flex; + justify-content: center; + align-items: center; + height: 64px; + background: #fff; + border-bottom: 1px solid #eee; + transition: all 0.2s; + + &.collapsed { + justify-content: flex-start; + padding-left: 20px; + } + } + + .Hamburger { + margin-bottom: 10px; + margin-right: 10px; + } } diff --git a/src/management-system-v2/components/content.tsx b/src/management-system-v2/components/content.tsx index 38216920f..f83459ed2 100644 --- a/src/management-system-v2/components/content.tsx +++ b/src/management-system-v2/components/content.tsx @@ -2,10 +2,11 @@ import styles from './content.module.scss'; import { FC, PropsWithChildren, ReactNode } from 'react'; -import { Layout as AntLayout, Grid, Button } from 'antd'; +import { Layout as AntLayout, Grid, Button, Image } from 'antd'; import { MenuOutlined } from '@ant-design/icons'; import cn from 'classnames'; import HeaderActions from './header-actions'; +import Link from 'next/link'; type ContentProps = PropsWithChildren<{ /** Top left title in the header (or custom node). */ @@ -18,8 +19,12 @@ type ContentProps = PropsWithChildren<{ wrapperClass?: string; /** Class name for the header. */ headerClass?: string; + + siderOpened?: boolean; }>; +//TODO: open and close hamburger menu + const Content: FC = ({ children, title, @@ -27,6 +32,7 @@ const Content: FC = ({ noHeader = false, wrapperClass, headerClass, + siderOpened, }) => { const breakpoint = Grid.useBreakpoint(); @@ -34,14 +40,32 @@ const Content: FC = ({ {noHeader ? null : ( + {/* Add icon into header for xs screens*/} + {breakpoint.xs ? ( +
+ + PROCEED Logo + +
+ ) : null} +
{title}
{breakpoint.xs ? ( // Hamburger menu for mobile view - , - , - ]} - > - {/*
Delete this process?
*/} - - - ); -}; - -export default ProcessDeleteSingleModal; diff --git a/src/management-system-v2/components/process-delete.module.scss b/src/management-system-v2/components/process-delete.module.scss deleted file mode 100644 index f0caca8bc..000000000 --- a/src/management-system-v2/components/process-delete.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -.Checkbox { - font-size: 12px; - width: 65%; -} - -.ClippedProcessTitle { - display: inline-block; - vertical-align: bottom; - width: 90%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} diff --git a/src/management-system-v2/components/process-delete.tsx b/src/management-system-v2/components/process-delete.tsx deleted file mode 100644 index 4d5033239..000000000 --- a/src/management-system-v2/components/process-delete.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { FC, useCallback, Dispatch, SetStateAction, Key, useTransition } from 'react'; -import { Button, Checkbox, Modal } from 'antd'; -import { useUserPreferences } from '@/lib/user-preferences'; -import styles from './process-delete.module.scss'; -import { deleteProcesses } from '@/lib/data/processes'; -import { useRouter } from 'next/navigation'; - -type ProcessDeleteModalType = { - onClose: () => void; - open: boolean; - processKeys: React.Key[]; - setSelection: Dispatch>; - processes: { definitionId: string; definitionName: string }[]; -}; - -const ProcessDeleteModal: FC = ({ - onClose, - open, - processKeys, - setSelection, - processes, -}) => { - const [isPending, startTransition] = useTransition(); - const router = useRouter(); - - const addPreferences = useUserPreferences.use.addPreferences(); - - const deleteSelectedProcesses = useCallback(() => { - startTransition(async () => { - await deleteProcesses(processKeys as string[]); - setSelection([]); - onClose(); - router.refresh(); - }); - }, [onClose, processKeys, router, setSelection]); - - return ( - <> - { - addPreferences({ 'ask-before-deleting-multiple': !e.target.checked }); - }} - > - Don't show again - , - , - , - ]} - > -
-
    - {processes - ?.filter((process) => processKeys.includes(process.definitionId)) - .sort((a, b) => a.definitionName.localeCompare(b.definitionName)) - .map((process) => { - return ( -
  • - {process.definitionName} -
  • - ); - })} -
-
-
- - ); -}; - -export default ProcessDeleteModal; diff --git a/src/management-system-v2/components/process-edit-button.tsx b/src/management-system-v2/components/process-edit-button.tsx deleted file mode 100644 index a46a50372..000000000 --- a/src/management-system-v2/components/process-edit-button.tsx +++ /dev/null @@ -1,81 +0,0 @@ -'use client'; - -import React, { ReactNode, useMemo, useState } from 'react'; - -import { Button } from 'antd'; -import type { ButtonProps } from 'antd'; -import ProcessModal from './process-modal'; -import { updateProcess } from '@/lib/data/processes'; -import { useRouter } from 'next/navigation'; -import { ProcessListProcess } from './processes'; - -type ProcessEditButtonProps = ButtonProps & { - process: ProcessListProcess; - wrapperElement?: ReactNode; -}; - -/** - * Button to edit Processes including a Modal for updating values. Alternatively, a custom wrapper element can be used instead of a button. - */ -const ProcessEditButton: React.FC = ({ - process, - wrapperElement, - ...props -}) => { - const [isProcessModalOpen, setIsProcessModalOpen] = useState(false); - const router = useRouter(); - const data = useMemo( - () => [ - { - ...process, - definitionName: process.definitionName.value, - description: process.description.value, - }, - ], - [process], - ); - - return ( - <> - {wrapperElement ? ( -
{ - setIsProcessModalOpen(true); - }} - > - {wrapperElement} -
- ) : ( - - )} - setIsProcessModalOpen(false)} - onSubmit={async (values) => { - const res = await updateProcess( - process.definitionId, - undefined, - values[0].description, - values[0].definitionName, - ); - // Let modal handle errors - if ('error' in res) { - return res; - } - setIsProcessModalOpen(false); - router.refresh(); - }} - /> - - ); -}; - -export default ProcessEditButton; diff --git a/src/management-system-v2/components/process-list.tsx b/src/management-system-v2/components/process-list.tsx index b1e8742e1..d7d88005e 100644 --- a/src/management-system-v2/components/process-list.tsx +++ b/src/management-system-v2/components/process-list.tsx @@ -27,13 +27,11 @@ import Preview from './previewProcess'; import useLastClickedStore from '@/lib/use-last-clicked-process-store'; import classNames from 'classnames'; import { generateDateString } from '@/lib/utils'; -import ProcessEditButton from './process-edit-button'; import { toCaslResource } from '@/lib/ability/caslAbility'; -import { useDeleteAsset, useInvalidateAsset, usePostAsset } from '@/lib/fetch-data'; import { useUserPreferences } from '@/lib/user-preferences'; -import { useAbilityStore } from '@/lib/abilityStore'; import { AuthCan } from '@/components/auth-can'; import { ProcessListProcess } from './processes'; +import ConfirmationButton from './confirmation-button'; type ProcessListProps = PropsWithChildren<{ data?: ProcessListProcess[]; @@ -42,10 +40,10 @@ type ProcessListProps = PropsWithChildren<{ isLoading?: boolean; onExportProcess: (processId: string) => void; onDeleteProcess: (processId: string) => void; + onEditProcess: (processId: string) => void; + onCopyProcess: (processId: string) => void; }>; -const ProcessActions = () => {}; - const ColumnHeader = [ 'Process Name', 'Description', @@ -65,19 +63,13 @@ const ProcessList: FC = ({ isLoading, onExportProcess, onDeleteProcess, + onEditProcess, + onCopyProcess, }) => { const router = useRouter(); - - const refreshData = useInvalidateAsset('/process'); - const [previewerOpen, setPreviewerOpen] = useState(false); - const [hovered, setHovered] = useState(undefined); - const [dropdownOpen, setDropdownOpen] = useState(false); - - const favourites = [0]; - const [previewProcess, setPreviewProcess] = useState(); const lastProcessId = useLastClickedStore((state) => state.processId); @@ -85,15 +77,8 @@ const ProcessList: FC = ({ const addPreferences = useUserPreferences.use.addPreferences(); const selectedColumns = useUserPreferences.use['process-list-columns'](); - const openModalWhenDeleteSingle = useUserPreferences.use['ask-before-deleting-single'](); - - const ability = useAbilityStore((state) => state.ability); - - const { mutateAsync: createProcess } = usePostAsset('/process'); - const { mutateAsync: deleteProcess } = useDeleteAsset('/process/{definitionId}', { - onSettled: refreshData, - }); + const favourites = [0]; const actionBarGenerator = useCallback( (record: ProcessListProcess) => { @@ -107,28 +92,16 @@ const ProcessList: FC = ({ }} /> - - { - createProcess({ - body: { - ...{ - ...record, - description: record.description.value, - definitionName: record.definitionName.value, - }, - bpmn: record.bpmn || '', - variables: [ - { - name: `${record.definitionName.value} Copy`, - type: '', - }, - ], - }, - }); - }} - /> - + + + { + e.stopPropagation(); + onCopyProcess(record.definitionId); + }} + /> + + { @@ -137,51 +110,34 @@ const ProcessList: FC = ({ /> - - - - } - /> + + { + onEditProcess(record.definitionId); + }} + /> + - {ability.can('delete', 'Process') && ( - - { - e.stopPropagation(); - if (openModalWhenDeleteSingle) { - onDeleteProcess(record.definitionId); - } else { - deleteProcess({ - params: { - path: { - definitionId: record.definitionId as string, - }, - }, - }); - } - setSelection(selection.filter((id) => id !== record.definitionId)); + {/*TODO: errors regarding query */} + + + + onDeleteProcess(record.definitionId)} + buttonProps={{ + icon: , + type: 'text', }} /> - )} + ); }, - [ - ability, - createProcess, - deleteProcess, - onDeleteProcess, - onExportProcess, - openModalWhenDeleteSingle, - router, - selection, - setSelection, - ], + [onCopyProcess, onDeleteProcess, onEditProcess, onExportProcess], ); // rowSelection object indicates the need for row selection @@ -281,7 +237,9 @@ const ProcessList: FC = ({ {record.definitionName.highlighted} ), + responsive: ['xs', 'sm'], }, + { title: 'Description', dataIndex: 'description', @@ -308,6 +266,7 @@ const ProcessList: FC = ({ {record.description.highlighted} ), + responsive: ['sm'], }, { @@ -325,7 +284,9 @@ const ProcessList: FC = ({ // router.push(`/processes/${record.definitionId}`); // }, }), + responsive: ['md'], }, + { title: 'Created On', dataIndex: 'createdOn', @@ -341,7 +302,9 @@ const ProcessList: FC = ({ // router.push(`/processes/${record.definitionId}`); // }, }), + responsive: ['md'], }, + { title: 'File Size', key: 'File Size', @@ -355,7 +318,9 @@ const ProcessList: FC = ({ // router.push(`/processes/${record.definitionId}`); // }, }), + responsive: ['md'], }, + { title: 'Owner', dataIndex: 'owner', @@ -370,7 +335,9 @@ const ProcessList: FC = ({ // router.push(`/processes/${record.definitionId}`); // }, }), + responsive: ['md'], }, + { fixed: 'right', width: 160, diff --git a/src/management-system-v2/components/processes.tsx b/src/management-system-v2/components/processes.tsx index 769d2289c..ad5561918 100644 --- a/src/management-system-v2/components/processes.tsx +++ b/src/management-system-v2/components/processes.tsx @@ -2,8 +2,8 @@ import styles from './processes.module.scss'; import React, { useCallback, useEffect, useState, useTransition } from 'react'; -import { Space, Button, Tooltip } from 'antd'; -import { ApiData, del, useDeleteAsset, useGetAsset, usePostAsset } from '@/lib/fetch-data'; +import { Space, Button, Tooltip, Grid, App } from 'antd'; +import { ApiData, usePostAsset } from '@/lib/fetch-data'; import { ExportOutlined, DeleteOutlined, @@ -11,7 +11,6 @@ import { AppstoreOutlined, CloseOutlined, } from '@ant-design/icons'; -import Fuse from 'fuse.js'; import IconView from './process-icon-list'; import ProcessList from './process-list'; import MetaData from './process-info-card'; @@ -19,7 +18,6 @@ import ProcessExportModal from './process-export'; import Bar from './bar'; import ProcessCreationButton from './process-creation-button'; import { useUserPreferences } from '@/lib/user-preferences'; -import { fetchProcessVersionBpmn } from '@/lib/process-queries'; import { setDefinitionsId, setDefinitionsName, @@ -27,13 +25,15 @@ import { setTargetNamespace, setDefinitionsVersionInformation, } from '@proceed/bpmn-helper'; -import ProcessDeleteModal from './process-delete'; -import ProcessDeleteSingleModal from './process-delete-single'; import { useAbilityStore } from '@/lib/abilityStore'; import useFuzySearch, { ReplaceKeysWithHighlighted } from '@/lib/useFuzySearch'; import { useRouter } from 'next/navigation'; -import { copyProcesses, deleteProcesses } from '@/lib/data/processes'; +import { copyProcesses, deleteProcesses, updateProcesses } from '@/lib/data/processes'; import ProcessModal from './process-modal'; +import { toCaslResource } from '@/lib/ability/caslAbility'; +import { AuthCan } from './auth-can'; +import ConfirmationButton from './confirmation-button'; +import ProcessImportButton from './process-import'; type Processes = ApiData<'/process', 'get'>; export type ProcessListProcess = ReplaceKeysWithHighlighted< @@ -74,39 +74,44 @@ type ProcessesProps = { const Processes = ({ processes }: ProcessesProps) => { const [selectedRowKeys, setSelectedRowKeys] = useState([]); - const [isPending, startTransition] = useTransition(); const router = useRouter(); + const { message } = App.useApp(); const addPreferences = useUserPreferences.use.addPreferences(); const iconView = useUserPreferences.use['icon-view-in-process-list'](); - const openModalWhenDeleteMultiple = useUserPreferences.use['ask-before-deleting-multiple'](); - const openModalWhenDeleteSingle = useUserPreferences.use['ask-before-deleting-single'](); - const openModalWhenCopy = useUserPreferences.use['ask-before-copying'](); const ability = useAbilityStore((state) => state.ability); - const { mutateAsync: addProcess } = usePostAsset('/process', {}); + const deleteSelectedProcesses = useCallback(async () => { + try { + const res = await deleteProcesses(selectedRowKeys as string[]); + // UserError + if (res && 'error' in res) { + return message.open({ + type: 'error', + content: res.error.message, + }); + } + } catch (e) { + // Unkown server error or was not sent from server (e.g. network error) + return message.open({ + type: 'error', + content: 'Someting went wrong while submitting the data', + }); + } + setSelectedRowKeys([]); + router.refresh(); + }, [message, router, selectedRowKeys]); - const deleteSelectedProcesses = useCallback(() => { - startTransition(async () => { - await deleteProcesses(selectedRowKeys as string[]); - setSelectedRowKeys([]); - router.refresh(); - }); - }, [router, selectedRowKeys]); + const breakpoint = Grid.useBreakpoint(); const [openExportModal, setOpenExportModal] = useState(false); const [openCopyModal, setOpenCopyModal] = useState(false); + const [openEditModal, setOpenEditModal] = useState(false); const [openDeleteModal, setOpenDeleteModal] = useState(false); const actionBar = ( <> - {/* - - */} - {/* - - */} { }} /> - {ability.can('delete', 'Process') && ( + + - { - if ( - (openModalWhenDeleteMultiple && selectedRowKeys.length > 1) || - (openModalWhenDeleteSingle && selectedRowKeys.length == 1) - ) { - setOpenDeleteModal(true); - } else { - deleteSelectedProcesses(); - } + setOpenDeleteModal(false)} + description="Are you sure you want to delete the selected processes?" + onConfirm={() => deleteSelectedProcesses()} + buttonProps={{ + icon: , + type: 'text', }} /> - )} + ); @@ -153,54 +157,31 @@ const Processes = ({ processes }: ProcessesProps) => { useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { + if (openCopyModal || openExportModal || openEditModal) { + return; + } + /* CTRL + A */ - if (e.ctrlKey && e.key === 'a' && !openCopyModal && !openDeleteModal && !openExportModal) { + if (e.ctrlKey && e.key === 'a') { e.preventDefault(); setSelectedRowKeys(filteredData?.map((item) => item.definitionId) ?? []); /* DEL */ - } else if (e.key === 'Delete') { + } else if (e.key === 'Delete' && selectedRowKeys.length) { if (ability.can('delete', 'Process')) { - if ( - (openModalWhenDeleteMultiple && selectedRowKeys.length > 1) || - (openModalWhenDeleteSingle && selectedRowKeys.length == 1) - ) { - setOpenDeleteModal(true); - } else { - deleteSelectedProcesses(); - } + setOpenDeleteModal(true); } /* ESC */ } else if (e.key === 'Escape') { deselectAll(); /* CTRL + C */ - } else if (e.ctrlKey && e.key === 'c' && !openCopyModal) { + } else if (e.ctrlKey && e.key === 'c') { if (ability.can('create', 'Process')) { setCopySelection(selectedRowKeys); } /* CTRL + V */ } else if (e.ctrlKey && e.key === 'v' && copySelection.length) { if (ability.can('create', 'Process')) { - if (openModalWhenCopy) { - setOpenCopyModal(true); - } else { - copySelection.forEach(async (key) => { - const process = processes?.find((item) => item.definitionId === key); - const processBpmn = await fetchProcessVersionBpmn(key as string); - - const newBPMN = await copyProcess({ - bpmn: processBpmn as string, - newName: `${process?.definitionName} (Copy)`, - }); - - addProcess({ - body: { - bpmn: newBPMN as string, - departments: [], - variables: [], - }, - }); - }); - } + setOpenCopyModal(true); } } }; @@ -213,16 +194,10 @@ const Processes = ({ processes }: ProcessesProps) => { copySelection, filteredData, selectedRowKeys, - deleteSelectedProcesses, - processes, - addProcess, - openModalWhenDeleteMultiple, - openModalWhenDeleteSingle, - openModalWhenCopy, ability, openCopyModal, - openDeleteModal, openExportModal, + openEditModal, ]); return ( @@ -273,6 +248,9 @@ const Processes = ({ processes }: ProcessesProps) => { New Process + } /> @@ -291,21 +269,27 @@ const Processes = ({ processes }: ProcessesProps) => { // TODO: Replace with server component loading state //isLoading={isLoading} onExportProcess={(id) => { - // TODO: If id is already selected, consider doing the batch - // operation on all selected instead of overwriting the - // selection with a single id. setOpenExportModal(true); setSelectedRowKeys([id]); }} - onDeleteProcess={(id) => { - setOpenDeleteModal(true); + onDeleteProcess={async (id) => { + await deleteProcesses([id]); + setSelectedRowKeys([]); + router.refresh(); + }} + onCopyProcess={(id) => { + setOpenCopyModal(true); + setSelectedRowKeys([id]); + }} + onEditProcess={(id) => { + setOpenEditModal(true); setSelectedRowKeys([id]); }} /> )} {/* Meta Data Panel */} - + {breakpoint.sm ? : null} ({ @@ -314,19 +298,6 @@ const Processes = ({ processes }: ProcessesProps) => { open={openExportModal} onClose={() => setOpenExportModal(false)} /> - setOpenDeleteModal(false)} - processKeys={selectedRowKeys} - setSelection={setSelectedRowKeys} - processes={processes} - open={openDeleteModal && selectedRowKeys.length > 1} - /> - setOpenDeleteModal(false)} - processKeys={selectedRowKeys} - setSelection={setSelectedRowKeys} - open={openDeleteModal && selectedRowKeys.length === 1} - /> 1 ? 'es' : ''}`} @@ -348,6 +319,27 @@ const Processes = ({ processes }: ProcessesProps) => { router.refresh(); }} /> + 1 ? 'es' : ''}`} + onCancel={() => setOpenEditModal(false)} + initialData={filteredData + .filter((process) => selectedRowKeys.includes(process.definitionId)) + .map((process) => ({ + definitionId: process.definitionId, + definitionName: process.definitionName.value, + description: process.description.value, + }))} + onSubmit={async (values) => { + const res = await updateProcesses(values); + // Errors are handled in the modal. + if (res && 'error' in res) { + return res; + } + setOpenEditModal(false); + router.refresh(); + }} + /> ); }; diff --git a/src/management-system-v2/lib/data/processes.tsx b/src/management-system-v2/lib/data/processes.tsx index 8436278a2..5db2e33bf 100644 --- a/src/management-system-v2/lib/data/processes.tsx +++ b/src/management-system-v2/lib/data/processes.tsx @@ -19,6 +19,9 @@ import { addDocumentation, generateDefinitionsId, setDefinitionsName } from '@pr import { createProcess, getFinalBpmn } from '../helpers/processHelpers'; import { UserErrorType, userError } from '../user-error'; import { ApiData } from '../fetch-data'; +// Antd uses barrel files, which next optimizes away. That requires us to import +// antd components directly from their files in this server actions file. +import Button from 'antd/es/button'; export const deleteProcesses = async (definitionIds: string[]) => { const processMetaObjects: any = getProcessMetaObjects(); @@ -26,14 +29,19 @@ export const deleteProcesses = async (definitionIds: string[]) => { // Get ability again since it might have changed. const { ability } = await getCurrentUser(); - await Promise.all( - definitionIds.map(async (definitionId) => { - const process = processMetaObjects[definitionId]; - if (process && ability.can('delete', toCaslResource('Process', process))) { - await removeProcess(definitionId); - } - }), - ); + for (const definitionId of definitionIds) { + const process = processMetaObjects[definitionId]; + + if (!process) { + return userError('A process with this id does not exist.', UserErrorType.NotFoundError); + } + + if (!ability.can('delete', toCaslResource('Process', process))) { + return userError('Not allowed to delete this process', UserErrorType.PermissionError); + } + + await removeProcess(definitionId); + } }; export const addProcesses = async ( @@ -103,6 +111,30 @@ export const updateProcess = async ( return toExternalFormat({ ...newProcessInfo, bpmn: newBpmn }); }; +export const updateProcesses = async ( + processes: { + definitionName?: string; + description?: string; + bpmn?: string; + definitionId: string; + }[], +) => { + const res = await Promise.all( + processes.map(async (process) => { + return await updateProcess( + process.definitionId, + process.bpmn, + process.description, + process.definitionName, + ); + }), + ); + + const firstError = res.find((r) => 'error' in r); + + return firstError ?? res; +}; + export const copyProcesses = async ( processes: { definitionName: string; diff --git a/src/management-system-v2/lib/user-preferences.ts b/src/management-system-v2/lib/user-preferences.ts index 02a8d26a7..c1750594b 100644 --- a/src/management-system-v2/lib/user-preferences.ts +++ b/src/management-system-v2/lib/user-preferences.ts @@ -27,10 +27,6 @@ const defaultPreferences = { */ 'icon-view-in-process-list': false, 'process-list-columns': ['', 'Process Name', 'Description', 'Last Edited'], - 'ask-before-deleting-multiple': true, - 'ask-before-deleting-single': true, - 'ask-before-copying': true, - 'process-copy-modal-accordion': true, 'role-page-side-panel': { open: false, width: 300 }, 'user-page-side-panel': { open: false, width: 300 }, 'process-meta-data': { open: false, width: 300 }, diff --git a/src/management-system-v2/scripts/genAntdCss.tsx b/src/management-system-v2/scripts/genAntdCss.tsx index ff802aaef..ce844e28c 100644 --- a/src/management-system-v2/scripts/genAntdCss.tsx +++ b/src/management-system-v2/scripts/genAntdCss.tsx @@ -22,7 +22,12 @@ import Theme from '../components/theme'; // To ensure the same class name calculation, we must use the same version here // as in the app. Therefore this package isn't listed in package.json as a // dependency, because we want to use the same as the current antd version. -import { createCache, extractStyle as extStyle, StyleProvider } from '@ant-design/cssinjs'; +import { + createCache, + extractStyle as extStyle, + StyleProvider, + px2remTransformer, +} from '@ant-design/cssinjs'; import * as antd from 'antd'; // These are client-only or style-less components that don't need to be included @@ -38,6 +43,10 @@ const blackList = [ 'Tour', ]; +const px2rem = px2remTransformer({ + rootValue: 16, // 32px = 1rem; @default 16 +}); + // A node containing all the relevant antd components. const Components = ( <> @@ -64,7 +73,7 @@ export function extractThemedStyle() { // Create a cache to store the styles of all components with the given themes. const cache = createCache(); renderToString( - + {Components} , );