Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add warning dialog when no prep available in disposal #4030

Merged
merged 9 commits into from
Sep 28, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import { H3 } from '../Atoms';
import { Button } from '../Atoms/Button';
import { Link } from '../Atoms/Link';
import { LoadingContext } from '../Core/Contexts';
import { toTable } from '../DataModel/helpers';
import type { SerializedResource } from '../DataModel/helperTypes';
import { getResourceViewUrl } from '../DataModel/resource';
import { schema } from '../DataModel/schema';
Expand Down Expand Up @@ -133,47 +132,35 @@ export function InteractionDialog({
else if (typeof recordSet === 'object')
loading(
getPrepsAvailableForLoanRs(recordSet.id).then((data) =>
availablePrepsReady(undefined, recordSet, data)
availablePrepsReady(undefined, data)
)
);
else
loading(
(catalogNumbers.length === 0
? Promise.resolve([])
: getPrepsAvailableForLoanCoIds('CatalogNumber', catalogNumbers)
).then((data) => availablePrepsReady(catalogNumbers, undefined, data))
).then((data) => availablePrepsReady(catalogNumbers, data))
);
}

const [prepsData, setPrepsData] = React.useState<RA<PreparationRow>>();

function availablePrepsReady(
entries: RA<string> | undefined,
recordSet: SerializedResource<RecordSet> | undefined,
prepsData: RA<PreparationRow>
) {
if (
prepsData.length === 0 &&
recordSet === undefined &&
typeof itemCollection === 'object'
) {
const item = new itemCollection.model.specifyModel.Resource();
f.maybe(toTable(item, 'LoanPreparation'), (loanPreparation) => {
loanPreparation.set('quantityReturned', 0);
loanPreparation.set('quantityResolved', 0);
});
itemCollection.add(item);
handleClose();
return;
}
const catalogNumbers = prepsData.map(([catalogNumber]) => catalogNumber);
const missing =
typeof entries === 'object'
? catalogNumbers.filter(
(catalogNumber) => !entries.includes(catalogNumber)
? entries.filter(
(entry) => !catalogNumbers.some((data) => data.includes(entry))
)
: [];

if (missing.length > 0) {
setState({ type: 'MissingState', missing });
setPrepsData(prepsData);
} else showPrepSelectDlg(prepsData);
}

Expand Down Expand Up @@ -237,14 +224,47 @@ export function InteractionDialog({
})}
</Dialog>
) : state.type === 'PreparationSelectState' ? (
<PrepDialog
action={action}
// BUG: make this readOnly if don't have necessary permissions
isReadOnly={false}
itemCollection={itemCollection}
preparations={state.entries}
onClose={handleClose}
/>
state.entries.length > 0 ? (
<PrepDialog
action={action}
// BUG: make this readOnly if don't have necessary permissions
isReadOnly={false}
itemCollection={itemCollection}
preparations={state.entries}
onClose={handleClose}
/>
) : (
<Dialog
buttons={
<>
<Button.DialogClose>{commonText.cancel()}</Button.DialogClose>
{typeof itemCollection === 'object' ? (
<Button.Info
onClick={() => {
itemCollection?.add(
new itemCollection.model.specifyModel.Resource()
);
handleClose();
}}
>
{interactionsText.continueWithoutPreparations()}
</Button.Info>
) : (
<Link.Blue href={getResourceViewUrl('Loan')}>
{interactionsText.continueWithoutPreparations()}
</Link.Blue>
)}
{}
</>
}
header={interactionsText.returnedPreparations({
tablePreparation: schema.models.Preparation.label,
})}
onClose={handleClose}
>
{interactionsText.noPreparationsWarning()}
</Dialog>
)
) : (
<RecordSetsDialog
isReadOnly
Expand All @@ -260,17 +280,31 @@ export function InteractionDialog({
{typeof itemCollection === 'object' ? (
<Button.Info
onClick={(): void => {
availablePrepsReady(undefined, undefined, []);
itemCollection?.add(
new itemCollection.model.specifyModel.Resource()
);
handleClose();
}}
>
{interactionsText.addUnassociated()}
</Button.Info>
) : model.name === 'Loan' || action.model.name === 'Loan' ? (
) : model.name === 'Loan' ||
(action.model.name === 'Loan' && prepsData?.length === 0) ? (
<Link.Blue href={getResourceViewUrl('Loan')}>
{interactionsText.withoutPreparations()}
</Link.Blue>
) : undefined}
{state.type === 'MissingState' &&
prepsData?.length !== 0 &&
prepsData ? (
<Button.Info
onClick={(): void => {
showPrepSelectDlg(prepsData);
}}
>
{interactionsText.continue()}
</Button.Info>
) : null}
</>
}
header={
Expand Down
39 changes: 21 additions & 18 deletions specifyweb/frontend/js_src/lib/components/Merging/Status.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
import React from 'react';
import { MergeStatus, StatusState, initialStatusState } from './types';
import type { LocalizedString } from 'typesafe-i18n';

import { commonText } from '../../localization/common';
import { mergingText } from '../../localization/merging';
import { ajax } from '../../utils/ajax';
import { MILLISECONDS } from '../Atoms/timeUnits';
import { softFail } from '../Errors/Crash';
import { LoadingContext } from '../Core/Contexts';
import { Dialog, dialogClassNames } from '../Molecules/Dialog';
import { Button } from '../Atoms/Button';
import { ping } from '../../utils/ajax/ping';
import { mergingText } from '../../localization/merging';
import { Label } from '../Atoms/Form';
import { Progress } from '../Atoms';
import { commonText } from '../../localization/common';
import { LocalizedString } from 'typesafe-i18n';
import { Button } from '../Atoms/Button';
import { Label } from '../Atoms/Form';
import { dialogIcons } from '../Atoms/Icons';
import { downloadFile } from '../Molecules/FilePicker';
import { MILLISECONDS } from '../Atoms/timeUnits';
import { LoadingContext } from '../Core/Contexts';
import { softFail } from '../Errors/Crash';
import { produceStackTrace } from '../Errors/stackTrace';
import { Dialog, dialogClassNames } from '../Molecules/Dialog';
import { downloadFile } from '../Molecules/FilePicker';
import type { MergeStatus, StatusState } from './types';
import { initialStatusState } from './types';

const statusLocalization: { [STATE in MergeStatus]: LocalizedString } = {
MERGING: mergingText.merging(),
ABORTED: mergingText.mergeFailed(),
FAILED: mergingText.mergeFailed(),
SUCCEEDED: mergingText.mergeSucceeded(),
};
const statusLocalization: { readonly [STATE in MergeStatus]: LocalizedString } =
{
MERGING: mergingText.merging(),
ABORTED: mergingText.mergeFailed(),
FAILED: mergingText.mergeFailed(),
SUCCEEDED: mergingText.mergeSucceeded(),
};

export function Status({
mergingId,
Expand Down Expand Up @@ -122,10 +125,10 @@ export function Status({
className={{ container: dialogClassNames.narrowContainer }}
dimensionsKey="merging-progress"
header={statusLocalization[state.status]}
onClose={undefined}
icon={
state.status === 'SUCCEEDED' ? dialogIcons.success : dialogIcons.error
}
onClose={undefined}
>
<Label.Block aria-atomic aria-live="polite" className="gap-2">
<div className="flex flex-col gap-2">
Expand Down
14 changes: 7 additions & 7 deletions specifyweb/frontend/js_src/lib/components/Merging/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { filterArray } from '../../utils/types';
import { multiSortFunction, removeKey } from '../../utils/utils';
import { Button } from '../Atoms/Button';
import { Input, Label } from '../Atoms/Form';
import { icons } from '../Atoms/Icons';
import { Link } from '../Atoms/Link';
import { Submit } from '../Atoms/Submit';
import { LoadingContext } from '../Core/Contexts';
Expand All @@ -37,10 +38,9 @@ import { formatUrl } from '../Router/queryString';
import { OverlayContext, OverlayLocation } from '../Router/Router';
import { autoMerge, postMergeResource } from './autoMerge';
import { CompareRecords } from './Compare';
import { Status } from './Status';
import { InvalidMergeRecordsDialog } from './InvalidMergeRecords';
import { recordMergingTableSpec } from './definitions';
import { icons } from '../Atoms/Icons';
import { InvalidMergeRecordsDialog } from './InvalidMergeRecords';
import { Status } from './Status';

export const mergingQueryParameter = 'records';

Expand Down Expand Up @@ -121,7 +121,7 @@ export function MergingDialog(): JSX.Element | null {
setIds(ids.filter((id) => !dismissedIds.includes(id)).join(','));

return model === undefined ? null : (
<RestrictMerge model={model} ids={ids} onDismiss={handleDismiss} />
<RestrictMerge ids={ids} model={model} onDismiss={handleDismiss} />
);
}

Expand Down Expand Up @@ -166,7 +166,7 @@ function RestrictMerge({
}
/>
) : (
<Merging records={records} model={model} onDismiss={handleDismiss} />
<Merging model={model} records={records} onDismiss={handleDismiss} />
);
}

Expand Down Expand Up @@ -427,11 +427,11 @@ export function MergeDialogContainer({
return (
<Dialog
buttons={buttons}
icon={icons.cog}
onClose={handleClose}
header={header}
// Disable gradient because table headers have solid backgrounds
specialMode="noGradient"
onClose={handleClose}
icon={icons.cog}
>
{children}
</Dialog>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useBooleanState } from '../../hooks/useBooleanState';
import { commonText } from '../../localization/common';
import { queryText } from '../../localization/query';
import { statsText } from '../../localization/stats';
import { cleanThrottledPromises } from '../../utils/ajax/throttledPromise';
import type { RA } from '../../utils/types';
import { H3, Ul } from '../Atoms';
import { Button } from '../Atoms/Button';
Expand All @@ -23,7 +24,6 @@ import type {
StatFormatterSpec,
StatLayout,
} from './types';
import { cleanThrottledPromises } from '../../utils/ajax/throttledPromise';

export function AddStatDialog({
defaultStatsAddLeft,
Expand Down
10 changes: 10 additions & 0 deletions specifyweb/frontend/js_src/lib/localization/interactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ export const interactionsText = createDictionary({
'uk-ua': 'Без препаратів',
'de-ch': 'Ohne Präparate',
},
continueWithoutPreparations: {
'en-us': 'Continue without preparations',
},
addUnassociated: {
'en-us': 'Add unassociated item',
'ru-ru': 'Добавить несвязанный элемент',
Expand Down Expand Up @@ -381,4 +384,11 @@ export const interactionsText = createDictionary({
'uk-ua': 'Редагувати {table:string}',
'de-ch': 'Bearbeiten {table:string}',
},
noPreparationsWarning: {
'en-us':
'None of these objects have preparations. Would you like to continue?',
},
continue: {
'en-us': 'Continue',
},
} as const);
2 changes: 1 addition & 1 deletion specifyweb/frontend/js_src/lib/localization/preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1819,7 +1819,7 @@ export const preferencesText = createDictionary({
},
detailedView: {
'en-us': 'Detailed view',
},
},
attachmentPreviewMode: {
'en-us': 'Attachment preview mode',
'de-ch': 'Anhang-Vorschaumodus',
Expand Down