diff --git a/src/management-system-v2/app/(dashboard)/layout.module.scss b/src/management-system-v2/app/(dashboard)/layout.module.scss index d6200931b..2e35726dc 100644 --- a/src/management-system-v2/app/(dashboard)/layout.module.scss +++ b/src/management-system-v2/app/(dashboard)/layout.module.scss @@ -46,6 +46,7 @@ /* Need this for the absolute editor page to work in non-parallel route. */ //min-height: 70vh; max-width: calc(100vw - 200px); + height: calc(100vh - 150px); &.collapsed { margin-left: 25px; diff --git a/src/management-system-v2/app/globals.css b/src/management-system-v2/app/globals.css index 396abdf60..80f1fe6d4 100644 --- a/src/management-system-v2/app/globals.css +++ b/src/management-system-v2/app/globals.css @@ -68,7 +68,6 @@ } } -/* TODO: Move to module */ .ant-menu-light.ant-menu-root.ant-menu-inline, .ant-menu-light > .ant-menu.ant-menu-root.ant-menu-inline, .ant-menu-light.ant-menu-root.ant-menu-vertical, @@ -110,8 +109,8 @@ } .card-selected { - background-color: #ebf8ff; - border: 1px solid #1976d2; + background-color: #ebf8ff !important; + border: 1px solid #1976d2 !important; } .no-select { @@ -124,3 +123,21 @@ .fit-height { height: calc(100vh - 64px - 70px); } + +/* Adapt BPMN.js toolbar styles */ +.djs-palette { + background-color: white !important; + border: solid 1px #d9d9d9 !important; + border-radius: 8px !important; + box-shadow: 0 2px 0 rgba(0, 0, 0, 0.02) !important; +} + +.group .entry { + border-bottom: solid 1px #d9d9d9; +} +.group .entry :last-child { + border-bottom: none; +} +.group .separator { + display: none; +} diff --git a/src/management-system-v2/components/content.module.scss b/src/management-system-v2/components/content.module.scss index 445748c41..735389176 100644 --- a/src/management-system-v2/components/content.module.scss +++ b/src/management-system-v2/components/content.module.scss @@ -1,5 +1,5 @@ .Main { - min-height: 100vh !important; + min-height: calc(100vh - 70px) !important; .Header { display: flex; @@ -23,6 +23,7 @@ padding: 40px 20px; max-width: 100%; background-color: white; + height: calc(100vh - 150px); &.compact { padding: 0; diff --git a/src/management-system-v2/components/layout.module.scss b/src/management-system-v2/components/layout.module.scss index 55506b1f0..0a4feef8d 100644 --- a/src/management-system-v2/components/layout.module.scss +++ b/src/management-system-v2/components/layout.module.scss @@ -1,6 +1,6 @@ .Sider { overflow: auto; - height: 100vh; + height: calc(100vh - 70px); //position: fixed !important; left: 0; top: 0; @@ -42,7 +42,7 @@ flex: auto; position: relative; /* Need this for the absolute editor page to work in non-parallel route. */ - min-height: 100vh; + // min-height: 100vh; } .Footer { diff --git a/src/management-system-v2/components/process-copy.module.scss b/src/management-system-v2/components/process-copy.module.scss new file mode 100644 index 000000000..47f0c0253 --- /dev/null +++ b/src/management-system-v2/components/process-copy.module.scss @@ -0,0 +1,13 @@ +.Checkbox { + font-size: 12px; + // width: 55%; +} + +.ClippedProcessTitle { + display: inline-block; + vertical-align: bottom; + width: 470px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} diff --git a/src/management-system-v2/components/process-copy.tsx b/src/management-system-v2/components/process-copy.tsx new file mode 100644 index 000000000..011f1a65a --- /dev/null +++ b/src/management-system-v2/components/process-copy.tsx @@ -0,0 +1,338 @@ +import { Button, Checkbox, Collapse, Input, Modal, Spin, Switch, Tooltip } from 'antd'; +import React, { Dispatch, FC, Key, SetStateAction, useCallback, useEffect, useState } from 'react'; +import styles from './process-copy.module.scss'; +import TextArea from 'antd/es/input/TextArea'; +import { useUserPreferences } from '@/lib/user-preferences'; +import { addUserPreference } from '@/lib/utils'; +import { useGetAsset, usePostAsset } from '@/lib/fetch-data'; +import type { CollapseProps } from 'antd'; +import { + generateDefinitionsId, + setDefinitionsId, + setDefinitionsName, + setDefinitionsVersionInformation, + setTargetNamespace, + addDocumentation, + getDefinitionsId, +} from '@proceed/bpmn-helper'; +import { fetchProcessVersionBpmn } from '@/lib/process-queries'; +import { LoadingOutlined } from '@ant-design/icons'; + +type ProcessCopyModalType = { + setCopyProcessIds: Dispatch> | Dispatch>; + processKeys: React.Key[] | String[]; + setSelection: Dispatch> | Dispatch>; +}; +type CopyProcessType = { + bpmn: string; + newName?: string; + newDescription?: string; + oldId: string | Key; +}; + +const ProcessCopyModal: FC = ({ + setCopyProcessIds, + processKeys, + setSelection, +}) => { + const { mutateAsync: addProcess } = usePostAsset('/process', { + onError: async (error, variables) => { + const id = await getDefinitionsId(variables.body.bpmn); + setFailed((prev) => [...prev, id as string]); + }, + onSuccess: (data) => { + setSelection((prev) => [...prev, data.definitionId as string]); + setSuccessful((prev) => [...prev, data.definitionId as string]); + }, + }); + + const [successful, setSuccessful] = useState([]); + const [failed, setFailed] = useState([]); + + const [loading, setLoading] = useState(false); + + const { preferences, addPreferences } = useUserPreferences(); + + const { 'process-copy-modal-accordion': isAccordion, 'ask-before-copying': copyModalPreference } = + preferences; + + const { data, refetch: refreshData } = useGetAsset('/process', { + params: { + query: { noBpmn: true }, + }, + }); + + const [bluePrintForProcesses, setBluePrintForProcesses] = useState( + data + ?.filter((process) => processKeys.includes(process.definitionId)) + .map((process) => { + return { + id: process.definitionId, + copyId: undefined, + name: `${process.definitionName} (Copy)`, + description: process.description, + originalName: process.definitionName, + originalDescription: process.description, + }; + }), + ); + + useEffect(() => { + setBluePrintForProcesses( + data + ?.filter((process) => processKeys.includes(process.definitionId)) + .map((process) => { + return { + id: process.definitionId, + copyId: undefined, + name: `${process.definitionName} (Copy)`, + description: process.description, + originalName: process.definitionName, + originalDescription: process.description, + }; + }), + ); + /* + Do not include data as dependency + The Blue-Print should only be determined by the processKeys + */ + }, [processKeys]); + + useEffect(() => { + /* Some Failed */ + if (processKeys.length && successful.length + failed.length === processKeys.length) { + setLoading(false); + } + /* All Successful */ + if (processKeys.length && successful.length === processKeys.length) { + setTimeout(() => { + setCopyProcessIds([]); + setFailed([]); + setSuccessful([]); + refreshData(); + }, 2_000); + } + }, [successful, failed, processKeys, setSelection, setCopyProcessIds, refreshData]); + + const copyProcess = useCallback( + async ({ bpmn, newName, newDescription, oldId }: CopyProcessType) => { + const newDefinitionsId = await generateDefinitionsId(); + let newBPMN = await setDefinitionsId(bpmn, newDefinitionsId); + setBluePrintForProcesses((prev) => { + return prev?.map((item) => { + if (item.id === oldId) { + return { ...item, copyId: newDefinitionsId }; + } + return item; + }); + }); + + newBPMN = await setDefinitionsName(newBPMN, newName || 'Copy of Process'); + newBPMN = await addDocumentation(newBPMN, newDescription || ''); + newBPMN = await setTargetNamespace(newBPMN, newDefinitionsId); + + newBPMN = await setDefinitionsVersionInformation(newBPMN, { + version: undefined, + versionName: undefined, + versionDescription: undefined, + versionBasedOn: undefined, + }); + + return newBPMN; + }, + [], + ); + + const copyProcesses = useCallback(() => { + bluePrintForProcesses?.forEach( + async ({ id, name, description, originalName, originalDescription }) => { + const processBpmn = await fetchProcessVersionBpmn(id as string); + + const newBPMN = await copyProcess({ + bpmn: processBpmn as string, + newName: name || `${originalName} (Copy)`, + newDescription: description || originalDescription, + oldId: id as string, + }); + + addProcess({ + body: { + bpmn: newBPMN as string, + departments: [], + variables: [], + }, + }).then(() => { + setSelection((prev) => prev.filter((key: string) => key !== id)); + }); + }, + ); + }, [addProcess, bluePrintForProcesses, copyProcess, setSelection]); + + const handleCopy = useCallback(() => { + if (failed.length) { + setSelection(failed); + setCopyProcessIds(failed); + setFailed([]); + setSuccessful([]); + refreshData(); + } + + setLoading(true); + copyProcesses(); + }, [copyProcesses, failed, refreshData, setCopyProcessIds, setSelection]); + + const handleCancel = useCallback(() => { + setCopyProcessIds([]); + setFailed([]); + setSuccessful([]); + setBluePrintForProcesses([]); + }, [setCopyProcessIds]); + + const items: CollapseProps['items'] = processKeys.map((id) => { + /* Initial */ + return { + key: id, + label: ( + + {bluePrintForProcesses?.find((e) => e.id == id)?.name} + + ), + children: ( + <> +
+ Process-Name: +
+ e.id == id)?.name}`} + onChange={(e) => { + setBluePrintForProcesses((prev) => { + return prev?.map((item) => { + if (item.id === id) { + return { ...item, name: e.target.value }; + } + return item; + }); + }); + }} + /> +
+ Process-Description: +
+ +