Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/views/course/object_duplications/new.json.jbuilder
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ json.destinationCourses @destination_courses do |course|
end)
end

json.destinationInstances @destination_instances do |instance|
sorted_destination_instances = @destination_instances.sort_by { |i| [i.id == current_tenant.id ? 0 : 1, i.name] }
json.destinationInstances sorted_destination_instances do |instance|
json.id instance.id
json.name instance.name
json.host instance.host
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import {
ControllerFieldState,
ControllerRenderProps,
FieldValues,
UseFormSetValue,
} from 'react-hook-form';
import { defineMessages, FormattedMessage } from 'react-intl';
import { Autocomplete, Box } from '@mui/material';
import MyLocation from '@mui/icons-material/MyLocation';
import { Autocomplete, Box, IconButton, Tooltip } from '@mui/material';

import {
selectDestinationInstances,
Expand All @@ -14,59 +16,89 @@ import {
import TextField from 'lib/components/core/fields/TextField';
import { formatErrorMessage } from 'lib/components/form/fields/utils/mapError';
import { useAppSelector } from 'lib/hooks/store';
import useTranslation from 'lib/hooks/useTranslation';

interface InstanceDropdownProps {
currentInstanceId: number;
disabled: boolean;
field: ControllerRenderProps<FieldValues, 'destination_instance_id'>;
fieldState: ControllerFieldState;
setValue: UseFormSetValue<FieldValues>;
}

const translations = defineMessages({
destinationInstance: {
id: 'course.duplication.Duplication.DestinationCourseSelector.InstanceDropdown.destinationInstance',
defaultMessage: 'Destination Instance',
defaultMessage: 'Destination instance',
},
currentInstance: {
id: 'course.duplication.Duplication.DestinationCourseSelector.InstanceDropdown.currentInstance',
defaultMessage: 'Select current instance',
},
});

const InstanceDropdown: FC<InstanceDropdownProps> = (props) => {
const { disabled, field, fieldState } = props;
const { currentInstanceId, disabled, field, fieldState, setValue } = props;
const instances = useAppSelector(selectDestinationInstances);
const metadata = useAppSelector(selectMetadata);
const instanceIds = useMemo(() => Object.keys(instances), [instances]);
const instanceIds = useMemo(
() =>
Object.keys(instances).toSorted(
(a, b) => instances[a].weight - instances[b].weight,
),
[instances],
);
const { t } = useTranslation();

return (
<Autocomplete
{...field}
disabled={
disabled ||
!metadata.canDuplicateToAnotherInstance ||
instances.length > 1
}
fullWidth
getOptionLabel={(instanceId): string => instances[instanceId]?.name ?? ''}
onChange={(_, instanceId): void =>
field.onChange(parseInt(instanceId, 10))
}
options={instanceIds}
renderInput={(inputProps): JSX.Element => (
<TextField
{...inputProps}
error={!!fieldState.error}
helperText={
fieldState.error && formatErrorMessage(fieldState.error.message)
}
label={<FormattedMessage {...translations.destinationInstance} />}
required
variant="standard"
/>
)}
renderOption={(optionProps, instanceId): JSX.Element => (
<Box component="li" {...optionProps} key={instanceId}>
{instances[instanceId]?.name ?? ''}
</Box>
)}
value={field.value?.toString()}
/>
<div className="flex">
<Autocomplete
{...field}
disabled={disabled || !metadata.canDuplicateToAnotherInstance}
fullWidth
getOptionLabel={(instanceId): string =>
instances[instanceId]?.name ?? ''
}
onChange={(_, instanceId): void =>
field.onChange(parseInt(instanceId, 10))
}
options={instanceIds}
renderInput={(inputProps): JSX.Element => (
<TextField
{...inputProps}
error={!!fieldState.error}
helperText={
fieldState.error && formatErrorMessage(fieldState.error.message)
}
label={<FormattedMessage {...translations.destinationInstance} />}
required
variant="standard"
/>
)}
renderOption={(optionProps, instanceId): JSX.Element => (
<Box component="li" {...optionProps} key={instanceId}>
{instances[instanceId]?.name ?? ''}
</Box>
)}
value={field.value?.toString()}
/>
<div className="flex items-end">
<Tooltip title={t(translations.currentInstance)}>
<IconButton
disabled={disabled}
onClick={() =>
setValue('destination_instance_id', currentInstanceId)
}
>
<MyLocation
className={`${
currentInstanceId === field.value ? 'text-blue-500' : ''
}`}
/>
</IconButton>
</Tooltip>
</div>
</div>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const NewCourseForm = (props) => {
handleSubmit,
setError,
formState: { errors },
setValue,
} = useForm({
mode: 'onSubmit',
defaultValues: initialValues,
Expand All @@ -56,9 +57,11 @@ const NewCourseForm = (props) => {
name="destination_instance_id"
render={({ field, fieldState }) => (
<InstanceDropdown
currentInstanceId={initialValues.destination_instance_id}
disabled={disabled}
field={field}
fieldState={fieldState}
setValue={setValue}
/>
)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ const selectDuplicationStore = (state: AppState): any => state.duplication;
export const selectDestinationInstances = createSelector(
selectDuplicationStore,
(duplicationStore) =>
duplicationStore.destinationInstances as DuplicationInstanceListData[],
duplicationStore.destinationInstances as Record<
number,
DuplicationInstanceListData
>,
);

export const selectMetadata = createSelector(
Expand Down
8 changes: 6 additions & 2 deletions client/app/bundles/course/duplication/store.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { produce } from 'immer';
import { arrayToObjectWithKey } from 'utilities/array';

import actionTypes, { duplicationModes } from 'course/duplication/constants';
import { getEmptySelectedItems, nestFolders } from 'course/duplication/utils';
Expand Down Expand Up @@ -64,7 +63,12 @@ const reducer = produce((state, action) => {
isLoading: false,
currentCourseId: data.sourceCourse.id,
destinationCourses: sortedDestinationCourses,
destinationInstances: arrayToObjectWithKey(destinationInstances, 'id'),
destinationInstances: Object.fromEntries(
destinationInstances.map((instance, index) => [
instance.id,
{ ...instance, weight: index },
]),
),
materialsComponent: nestedFolders,
};
}
Expand Down