From d30230f1220baea12d5e842975fe04d769ed5488 Mon Sep 17 00:00:00 2001 From: Joel Davies Date: Fri, 8 Dec 2023 15:44:20 +0000 Subject: [PATCH 1/9] Initial implementation of copy systems #136 --- src/api/systems.tsx | 84 +++++++++++++++++-- src/app.types.tsx | 9 ++ .../systemDirectoryDialog.component.tsx | 43 ++++++++-- src/systems/systems.component.tsx | 45 +++++++++- 4 files changed, 166 insertions(+), 15 deletions(-) diff --git a/src/api/systems.tsx b/src/api/systems.tsx index 420bd514a..4dccccac7 100644 --- a/src/api/systems.tsx +++ b/src/api/systems.tsx @@ -9,6 +9,7 @@ import axios, { AxiosError } from 'axios'; import { AddSystem, BreadcrumbsInfo, + CopyToSystem, EditSystem, ErrorParsing, MoveToSystem, @@ -243,10 +244,10 @@ export const useMoveToSystem = (): UseMutationResult< return useMutation(async (moveToSystem: MoveToSystem) => { const transferStates: TransferState[] = []; - let successfulIds: string[] = []; + const successfulIds: string[] = []; const promises = moveToSystem.selectedSystems.map( - async (system: System, index: number) => { + async (system: System) => { return editSystem({ id: system.id, parent_id: moveToSystem.targetSystem?.id || null, @@ -255,7 +256,7 @@ export const useMoveToSystem = (): UseMutationResult< const targetSystemName = moveToSystem.targetSystem?.name || 'Root'; transferStates.push({ name: result.name, - message: `Successfully moved to ${targetSystemName}`, + message: `Successfully ${system.name} moved to ${targetSystemName}`, state: 'success', }); @@ -265,7 +266,7 @@ export const useMoveToSystem = (): UseMutationResult< const response = error.response?.data as ErrorParsing; transferStates.push({ - name: moveToSystem.selectedSystems[index].name, + name: system.name, message: response.detail, state: 'error', }); @@ -276,9 +277,11 @@ export const useMoveToSystem = (): UseMutationResult< await Promise.all(promises); if (successfulIds.length > 0) { - queryClient.invalidateQueries({ queryKey: ['Systems'] }); + queryClient.invalidateQueries({ + queryKey: ['Systems', moveToSystem.targetSystem?.id || null], + }); queryClient.invalidateQueries({ queryKey: ['SystemBreadcrumbs'] }); - successfulIds.map((id: string) => + successfulIds.forEach((id: string) => queryClient.invalidateQueries({ queryKey: ['System', id] }) ); } @@ -286,3 +289,72 @@ export const useMoveToSystem = (): UseMutationResult< return transferStates; }); }; + +export const useCopyToSystem = (): UseMutationResult< + TransferState[], + AxiosError, + CopyToSystem +> => { + const queryClient = useQueryClient(); + + const successfulIds: string[] = []; + + return useMutation(async (copyToSystem: CopyToSystem) => { + const transferStates: TransferState[] = []; + + const promises = copyToSystem.selectedSystems.map( + async (system: System) => { + // Information to post (backend will just ignore the extra here - only id and code) + const systemAdd: AddSystem = system as AddSystem; + + // Assing new parent + systemAdd.parent_id = copyToSystem.targetSystem?.id || null; + + // Avoid duplicates by appending _copy_n for nth copy + if (copyToSystem.existingSystemCodes.includes(system.code)) { + let count = 1; + let newName = system.name; + let newCode = system.code; + + while (copyToSystem.existingSystemCodes.includes(newCode)) { + newName = `${system.name}_copy_${count}`; + newCode = `${system.code}_copy_${count}`; + count++; + } + + systemAdd.name = newName; + } + + return addSystem(systemAdd) + .then((result: System) => { + const targetSystemName = copyToSystem.targetSystem?.name || 'Root'; + transferStates.push({ + name: result.name, + message: `Successfully copied ${system.name} to ${targetSystemName}`, + state: 'success', + }); + + successfulIds.push(result.id); + }) + .catch((error) => { + const response = error.response?.data as ErrorParsing; + + transferStates.push({ + name: system.name, + message: response.detail, + state: 'error', + }); + }); + } + ); + + await Promise.all(promises); + + if (successfulIds.length > 0) + queryClient.invalidateQueries({ + queryKey: ['Systems', copyToSystem.targetSystem?.id || 'null'], + }); + + return transferStates; + }); +}; diff --git a/src/app.types.tsx b/src/app.types.tsx index 352f40480..28796a0f9 100644 --- a/src/app.types.tsx +++ b/src/app.types.tsx @@ -199,3 +199,12 @@ export interface MoveToSystem { // Null if root targetSystem: System | null; } + +export interface CopyToSystem { + selectedSystems: System[]; + // Null if root + targetSystem: System | null; + // Existing known system codes at the destination + // (for appending to the names to avoid duplication) + existingSystemCodes: String[]; +} diff --git a/src/systems/systemDirectoryDialog.component.tsx b/src/systems/systemDirectoryDialog.component.tsx index 83fd0da87..ac0cdb894 100644 --- a/src/systems/systemDirectoryDialog.component.tsx +++ b/src/systems/systemDirectoryDialog.component.tsx @@ -8,6 +8,7 @@ import { } from '@mui/material'; import React from 'react'; import { + useCopyToSystem, useMoveToSystem, useSystem, useSystems, @@ -24,10 +25,12 @@ export interface SystemDirectoryDialogProps { selectedSystems: System[]; onChangeSelectedSystems: (selectedSystems: System[]) => void; parentSystemId: string | null; + type: 'moveTo' | 'copyTo'; } export const SystemDirectoryDialog = (props: SystemDirectoryDialogProps) => { - const { open, onClose, selectedSystems, onChangeSelectedSystems } = props; + const { open, onClose, selectedSystems, onChangeSelectedSystems, type } = + props; // Store here and update only if changed to reduce re-renders and allow // navigation @@ -49,6 +52,7 @@ export const SystemDirectoryDialog = (props: SystemDirectoryDialogProps) => { useSystem(parentSystemId); const { mutateAsync: moveToSystem } = useMoveToSystem(); + const { mutateAsync: copyToSystem } = useCopyToSystem(); const handleClose = React.useCallback(() => { onClose(); @@ -79,6 +83,34 @@ export const SystemDirectoryDialog = (props: SystemDirectoryDialogProps) => { targetSystemLoading, ]); + const handleCopyTo = React.useCallback(() => { + console.log(systemsData); + if ((!targetSystemLoading || parentSystemId === null) && systemsData) { + const existingSystemCodes = systemsData.map((system) => system.code); + + copyToSystem({ + selectedSystems: selectedSystems, + // Only reason for targetSystem to be undefined here is if not loading at all + // which happens when at root + targetSystem: targetSystem || null, + existingSystemCodes: existingSystemCodes, + }).then((response) => { + handleTransferState(response); + onChangeSelectedSystems([]); + handleClose(); + }); + } + }, [ + copyToSystem, + handleClose, + onChangeSelectedSystems, + parentSystemId, + selectedSystems, + systemsData, + targetSystem, + targetSystemLoading, + ]); + return ( { <> - Move{' '} + {type === 'moveTo' ? 'Move ' : 'Copy '} {selectedSystems.length > 1 ? `${selectedSystems.length} systems` : '1 system'}{' '} @@ -124,11 +156,12 @@ export const SystemDirectoryDialog = (props: SystemDirectoryDialogProps) => { disabled={ // Disable when not moving anywhere different selectedSystems.length > 0 && - selectedSystems[0].parent_id === parentSystemId + selectedSystems[0].parent_id === parentSystemId && + type === 'moveTo' } - onClick={handleMoveTo} + onClick={type === 'moveTo' ? handleMoveTo : handleCopyTo} > - Move here + {type === 'moveTo' ? 'Move' : 'Copy'} here diff --git a/src/systems/systems.component.tsx b/src/systems/systems.component.tsx index 706f2302b..6e770ab22 100644 --- a/src/systems/systems.component.tsx +++ b/src/systems/systems.component.tsx @@ -2,6 +2,7 @@ import { NavigateNext } from '@mui/icons-material'; import AddIcon from '@mui/icons-material/Add'; import ClearIcon from '@mui/icons-material/Clear'; import DriveFileMoveOutlinedIcon from '@mui/icons-material/DriveFileMoveOutlined'; +import FolderCopyOutlinedIcon from '@mui/icons-material/FolderCopyOutlined'; import { Box, Button, @@ -68,7 +69,7 @@ const MoveSystemsButton = (props: { onChangeSelectedSystems: (selectedSystems: System[]) => void; parentSystemId: string | null; }) => { - const [moveSystemDialogOpen, setMoveSystemDialogOpen] = + const [moveSystemsDialogOpen, setMoveSystemsDialogOpen] = React.useState(false); return ( @@ -77,16 +78,47 @@ const MoveSystemsButton = (props: { sx={{ mx: 1 }} variant="outlined" startIcon={} - onClick={() => setMoveSystemDialogOpen(true)} + onClick={() => setMoveSystemsDialogOpen(true)} > Move to setMoveSystemDialogOpen(false)} + open={moveSystemsDialogOpen} + onClose={() => setMoveSystemsDialogOpen(false)} selectedSystems={props.selectedSystems} onChangeSelectedSystems={props.onChangeSelectedSystems} parentSystemId={props.parentSystemId} + type="moveTo" + /> + + ); +}; + +const CopySystemsButton = (props: { + selectedSystems: System[]; + onChangeSelectedSystems: (selectedSystems: System[]) => void; + parentSystemId: string | null; +}) => { + const [copySystemsDialogOpen, setCopySystemsDialogOpen] = + React.useState(false); + + return ( + <> + + setCopySystemsDialogOpen(false)} + selectedSystems={props.selectedSystems} + onChangeSelectedSystems={props.onChangeSelectedSystems} + parentSystemId={props.parentSystemId} + type="copyTo" /> ); @@ -176,6 +208,11 @@ function Systems() { onChangeSelectedSystems={setSelectedSystems} parentSystemId={systemId} /> +