diff --git a/rsconcept/backend/requirements-dev-lock.txt b/rsconcept/backend/requirements-dev-lock.txt index 70c133f2..5936f60f 100644 --- a/rsconcept/backend/requirements-dev-lock.txt +++ b/rsconcept/backend/requirements-dev-lock.txt @@ -16,4 +16,4 @@ djangorestframework-stubs==3.15.1 django-extensions==3.2.3 mypy==1.11.2 pylint==3.3.1 -coverage==7.6.3 \ No newline at end of file +coverage==7.6.4 \ No newline at end of file diff --git a/rsconcept/backend/requirements.txt b/rsconcept/backend/requirements.txt index ab8455c0..7144bc0a 100644 --- a/rsconcept/backend/requirements.txt +++ b/rsconcept/backend/requirements.txt @@ -1,14 +1,14 @@ -tzdata==2024.1 -Django==5.1.1 +tzdata==2024.2 +Django==5.1.2 djangorestframework==3.15.2 -django-cors-headers==4.4.0 +django-cors-headers==4.5.0 django-filter==24.3 drf-spectacular==0.27.2 drf-spectacular-sidecar==2024.7.1 coreapi==2.3.3 -django-rest-passwordreset==1.4.1 +django-rest-passwordreset==1.4.2 cctext==0.1.4 -pyconcept==0.1.10 +pyconcept==0.1.11 -psycopg2-binary==2.9.9 +psycopg2-binary==2.9.10 gunicorn==23.0.0 \ No newline at end of file diff --git a/rsconcept/frontend/src/components/DomainIcons.tsx b/rsconcept/frontend/src/components/DomainIcons.tsx index 8798d645..c4ab334d 100644 --- a/rsconcept/frontend/src/components/DomainIcons.tsx +++ b/rsconcept/frontend/src/components/DomainIcons.tsx @@ -20,6 +20,8 @@ import { IconGraphInputs, IconGraphOutputs, IconHide, + IconMoveDown, + IconMoveUp, IconOSS, IconPrivate, IconProps, @@ -159,3 +161,11 @@ export function CstTypeIcon({ value, size = '1.25rem', className }: DomIconProps return ; } } + +export function RelocateUpIcon({ value, size = '1.25rem', className }: DomIconProps) { + if (value) { + return ; + } else { + return ; + } +} diff --git a/rsconcept/frontend/src/components/select/PickMultiConstituenta.tsx b/rsconcept/frontend/src/components/select/PickMultiConstituenta.tsx index fbf103ea..1fa64b5b 100644 --- a/rsconcept/frontend/src/components/select/PickMultiConstituenta.tsx +++ b/rsconcept/frontend/src/components/select/PickMultiConstituenta.tsx @@ -23,6 +23,7 @@ interface PickMultiConstituentaProps { prefixID: string; rows?: number; + noBorder?: boolean; selected: ConstituentaID[]; setSelected: React.Dispatch>; @@ -36,6 +37,7 @@ function PickMultiConstituenta({ data, prefixID, rows, + noBorder, selected, setSelected }: PickMultiConstituentaProps) { @@ -118,10 +120,10 @@ function PickMultiConstituenta({ ); return ( -
-
+
+
- Выбраны {selected.length} из {data.length} + {data.length > 0 ? `Выбраны ${selected.length} из ${data.length}` : 'Конституенты'}
{ oss: IOperationSchema; - target: IOperation; + initialTarget?: IOperation; onSubmit: (data: ICstRelocateData) => void; } -function DlgRelocateConstituents({ oss, hideWindow, target, onSubmit }: DlgRelocateConstituentsProps) { +function DlgRelocateConstituents({ oss, hideWindow, initialTarget, onSubmit }: DlgRelocateConstituentsProps) { const library = useLibrary(); - const schemas = useMemo(() => { - const node = oss.graph.at(target.id)!; - const ids: LibraryItemID[] = [ - ...node.inputs.map(id => oss.operationByID.get(id)!.result).filter(id => id !== null), - ...node.outputs.map(id => oss.operationByID.get(id)!.result).filter(id => id !== null) - ]; - return ids.map(id => library.items.find(item => item.id === id)).filter(item => item !== undefined); - }, [oss, library.items, target.id]); + const [directionUp, setDirectionUp] = useState(true); const [destination, setDestination] = useState(undefined); const [selected, setSelected] = useState([]); + const [source, setSource] = useState( + library.items.find(item => item.id === initialTarget?.result) + ); - const source = useRSFormDetails({ target: String(target.result!) }); - const filtered = useMemo(() => { - if (!source.schema || !destination) { + const operation = useMemo(() => oss.items.find(item => item.result === source?.id), [oss, source]); + const sourceSchemas = useMemo(() => library.items.filter(item => oss.schemas.includes(item.id)), [library, oss]); + const destinationSchemas = useMemo(() => { + if (!operation) { + return []; + } + const node = oss.graph.at(operation.id)!; + const ids: LibraryItemID[] = directionUp + ? node.inputs.map(id => oss.operationByID.get(id)!.result).filter(id => id !== null) + : node.outputs.map(id => oss.operationByID.get(id)!.result).filter(id => id !== null); + return ids.map(id => library.items.find(item => item.id === id)).filter(item => item !== undefined); + }, [oss, library.items, operation, directionUp]); + + const sourceData = useRSFormDetails({ target: source ? String(source.id) : undefined }); + const filteredConstituents = useMemo(() => { + if (!sourceData.schema || !destination || !operation) { return []; } const destinationOperation = oss.items.find(item => item.result === destination.id); - return getRelocateCandidates(target.id, destinationOperation!.id, source.schema, oss); - }, [destination, target.id, source.schema, oss]); + return getRelocateCandidates(operation.id, destinationOperation!.id, sourceData.schema, oss); + }, [destination, operation, sourceData.schema, oss]); const isValid = useMemo(() => !!destination && selected.length > 0, [destination, selected]); + const toggleDirection = useCallback(() => { + setDirectionUp(prev => !prev); + setDestination(undefined); + }, []); - useLayoutEffect(() => { + const handleSelectSource = useCallback((newValue: ILibraryItem | undefined) => { + setSource(newValue); + setDestination(undefined); setSelected([]); - }, [destination]); + }, []); const handleSelectDestination = useCallback((newValue: ILibraryItem | undefined) => { setDestination(newValue); + setSelected([]); }, []); const handleSubmit = useCallback(() => { @@ -74,24 +92,44 @@ function DlgRelocateConstituents({ oss, hideWindow, target, onSubmit }: DlgReloc onSubmit={handleSubmit} className={clsx('w-[40rem] h-[33rem]', 'py-3 px-6')} > - - - {source.schema ? ( - +
+ - ) : null} - + } + onClick={toggleDirection} + /> + +
+ + {sourceData.schema ? ( + + ) : null} + +
); } diff --git a/rsconcept/frontend/src/pages/OssPage/MenuOssTabs.tsx b/rsconcept/frontend/src/pages/OssPage/MenuOssTabs.tsx index 38215c4c..056416df 100644 --- a/rsconcept/frontend/src/pages/OssPage/MenuOssTabs.tsx +++ b/rsconcept/frontend/src/pages/OssPage/MenuOssTabs.tsx @@ -4,6 +4,7 @@ import { urls } from '@/app/urls'; import { IconAdmin, IconAlert, + IconChild, IconDestroy, IconEdit2, IconEditor, @@ -67,6 +68,11 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) { router.push(urls.login); } + function handleRelocate() { + editMenu.hide(); + controller.promptRelocateConstituents(undefined, []); + } + return (
@@ -128,9 +134,11 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) { /> } + disabled={controller.isProcessing} + onClick={handleRelocate} />
diff --git a/rsconcept/frontend/src/pages/OssPage/OssEditContext.tsx b/rsconcept/frontend/src/pages/OssPage/OssEditContext.tsx index 2f23e672..3d82786c 100644 --- a/rsconcept/frontend/src/pages/OssPage/OssEditContext.tsx +++ b/rsconcept/frontend/src/pages/OssPage/OssEditContext.tsx @@ -76,7 +76,7 @@ export interface IOssEditContext extends ILibraryItemEditor { promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void; promptEditOperation: (target: OperationID, positions: IOperationPosition[]) => void; executeOperation: (target: OperationID, positions: IOperationPosition[]) => void; - promptRelocateConstituents: (target: OperationID, positions: IOperationPosition[]) => void; + promptRelocateConstituents: (target: OperationID | undefined, positions: IOperationPosition[]) => void; } const OssEditContext = createContext(null); @@ -360,7 +360,7 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit [model] ); - const promptRelocateConstituents = useCallback((target: OperationID, positions: IOperationPosition[]) => { + const promptRelocateConstituents = useCallback((target: OperationID | undefined, positions: IOperationPosition[]) => { setPositions(positions); setTargetOperationID(target); setShowRelocateConstituents(true); @@ -368,9 +368,18 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit const handleRelocateConstituents = useCallback( (data: ICstRelocateData) => { - model.savePositions({ positions: positions }, () => - model.relocateConstituents(data, () => toast.success(information.changesSaved)) - ); + if ( + positions.every(item => { + const operation = model.schema!.operationByID.get(item.id)!; + return operation.position_x === item.position_x && operation.position_y === item.position_y; + }) + ) { + model.relocateConstituents(data, () => toast.success(information.changesSaved)); + } else { + model.savePositions({ positions: positions }, () => + model.relocateConstituents(data, () => toast.success(information.changesSaved)) + ); + } }, [model, positions] ); @@ -458,7 +467,7 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit {showRelocateConstituents ? ( setShowRelocateConstituents(false)} - target={targetOperation!} + initialTarget={targetOperation} oss={model.schema} onSubmit={handleRelocateConstituents} />