Skip to content

Commit 7646cdb

Browse files
Merge branch 'issue-7138' of https://github.com/specify/specify7 into issue-7138
2 parents baeadcf + e3b098f commit 7646cdb

File tree

1 file changed

+81
-27
lines changed

1 file changed

+81
-27
lines changed

specifyweb/frontend/js_src/lib/components/Toolbar/TreeRepair.tsx

Lines changed: 81 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,10 @@ export function TreeSelectDialog({
148148
/>
149149
)}
150150
{permissionName === 'repair' && (
151-
<TreeActionsDropdown treeDefinition={treeDefinition} treeName={treeName} />
151+
<TreeActionsDropdown
152+
treeDefinition={treeDefinition}
153+
treeName={treeName}
154+
/>
152155
)}
153156
</div>
154157
</div>
@@ -161,15 +164,35 @@ export function TreeSelectDialog({
161164
) : null;
162165
}
163166

164-
function ActionsMenu({ treeName, treeDefinition }: { readonly treeName: string; readonly treeDefinition: any }): JSX.Element | null {
165-
const [result, setResult] = React.useState<{ readonly accepted: number; readonly synonyms: number; readonly total: number } | null>(null);
167+
function ActionsMenu({
168+
treeName,
169+
treeDefinition,
170+
}: {
171+
readonly treeName: string;
172+
readonly treeDefinition: any;
173+
}): JSX.Element | null {
174+
const [result, setResult] = React.useState<{
175+
readonly accepted: number;
176+
readonly synonyms: number;
177+
readonly total: number;
178+
} | null>(null);
166179
const [isRunning, setIsRunning] = React.useState(false);
167-
const [repairStatus, setRepairStatus] = React.useState<'idle' | 'success'>('idle');
168-
const [hoveredAction, setHoveredAction] = React.useState<'rebuildAccepted' | 'rebuildSynonyms' | 'repair' | null>(null);
180+
const [repairStatus, setRepairStatus] = React.useState<'idle' | 'success'>(
181+
'idle'
182+
);
183+
const [hoveredAction, setHoveredAction] = React.useState<
184+
'rebuildAccepted' | 'rebuildSynonyms' | 'repair' | null
185+
>(null);
169186
if (typeof treeDefinition !== 'object') return null;
170187
if (!(treeName in TREE_RESOURCES)) return null;
171-
const canRebuild = hasPermission(TREE_RESOURCES[treeName as TreeNameKey], 'rebuild_full_names');
172-
const canRepair = hasPermission(TREE_RESOURCES[treeName as TreeNameKey], 'repair');
188+
const canRebuild = hasPermission(
189+
TREE_RESOURCES[treeName as TreeNameKey],
190+
'rebuild_full_names'
191+
);
192+
const canRepair = hasPermission(
193+
TREE_RESOURCES[treeName as TreeNameKey],
194+
'repair'
195+
);
173196
if (!canRebuild && !canRepair) return null;
174197
const id = treeDefinition.get('id');
175198
interface RebuildChanged { readonly accepted: number; readonly synonyms: number; readonly total: number }
@@ -196,7 +219,11 @@ function ActionsMenu({ treeName, treeDefinition }: { readonly treeName: string;
196219
setRepairStatus('idle');
197220
ajax<unknown>(
198221
`/api/specify_tree/${treeName.toLowerCase()}/${id}/rebuild-full-name${withSynonyms ? '?rebuild_synonyms=true' : ''}`,
199-
{ method: 'POST', headers: { Accept: 'application/json' }, errorMode: 'dismissible' }
222+
{
223+
method: 'POST',
224+
headers: { Accept: 'application/json' },
225+
errorMode: 'dismissible',
226+
}
200227
)
201228
.then((resp) => setResult(parseRebuildResponse(resp)))
202229
.finally(() => setIsRunning(false));
@@ -220,7 +247,7 @@ function ActionsMenu({ treeName, treeDefinition }: { readonly treeName: string;
220247
readonly label: () => LocalizedString;
221248
readonly description: () => LocalizedString;
222249
readonly run: () => void;
223-
}
250+
};
224251
const actions: readonly ActionDef[] = [
225252
{
226253
key: 'repair',
@@ -247,25 +274,33 @@ function ActionsMenu({ treeName, treeDefinition }: { readonly treeName: string;
247274
const visibleActions = actions.filter((a) => a.can);
248275

249276
let status: React.ReactNode = null;
250-
if (isRunning) status = <span className="text-xs">{commonText.working()}</span>;
277+
if (isRunning)
278+
status = <span className="text-xs">{commonText.working()}</span>;
251279
else if (result && canRebuild) {
252-
status = result.total > 0 ? (
253-
<div className="text-xs">
254-
{treeText.rebuildResult({ total: result.total, accepted: result.accepted, synonyms: result.synonyms })}
255-
</div>
256-
) : (
257-
<div className="text-xs italic">{treeText.noFullNamesUpdated()}</div>
258-
);
280+
status =
281+
result.total > 0 ? (
282+
<div className="text-xs">
283+
{treeText.rebuildResult({
284+
total: result.total,
285+
accepted: result.accepted,
286+
synonyms: result.synonyms,
287+
})}
288+
</div>
289+
) : (
290+
<div className="text-xs italic">{treeText.noFullNamesUpdated()}</div>
291+
);
259292
} else if (repairStatus === 'success' && (!canRebuild || !result)) {
260293
status = (
261294
<div className="text-xs text-green-600 dark:text-green-400">
262-
{headerText.treeRepairComplete()}
295+
{headerText.treeRepairComplete()}
263296
</div>
264297
);
265298
} else if (hoveredAction) {
266299
const current = actions.find((a) => a.key === hoveredAction);
267300
status = current ? (
268-
<div className="text-xs leading-snug opacity-80">{current.description()}</div>
301+
<div className="text-xs leading-snug opacity-80">
302+
{current.description()}
303+
</div>
269304
) : null;
270305
}
271306
return (
@@ -275,9 +310,14 @@ function ActionsMenu({ treeName, treeDefinition }: { readonly treeName: string;
275310
<Button.Secondary
276311
disabled={isRunning}
277312
key={a.key}
278-
onClick={(e): void => { e.preventDefault(); a.run(); }}
313+
onClick={(e): void => {
314+
e.preventDefault();
315+
a.run();
316+
}}
279317
onMouseEnter={(): void => setHoveredAction(a.key)}
280-
onMouseLeave={(): void => setHoveredAction((h) => (h === a.key ? null : h))}
318+
onMouseLeave={(): void =>
319+
setHoveredAction((h) => (h === a.key ? null : h))
320+
}
281321
>
282322
<>{a.label()}</>
283323
</Button.Secondary>
@@ -288,33 +328,47 @@ function ActionsMenu({ treeName, treeDefinition }: { readonly treeName: string;
288328
);
289329
}
290330

291-
function TreeActionsDropdown({ treeName, treeDefinition }: { readonly treeName: string; readonly treeDefinition: any }): JSX.Element | null {
331+
function TreeActionsDropdown({
332+
treeName,
333+
treeDefinition,
334+
}: {
335+
readonly treeName: string;
336+
readonly treeDefinition: any;
337+
}): JSX.Element | null {
292338
const [open, setOpen] = React.useState(false);
293339
const anchorRef = React.useRef<HTMLButtonElement | null>(null);
294340
const menuRef = React.useRef<HTMLDivElement | null>(null);
295-
const [position, setPosition] = React.useState<{ readonly top: number; readonly left: number } | null>(null);
341+
const [position, setPosition] = React.useState<{
342+
readonly top: number;
343+
readonly left: number;
344+
} | null>(null);
296345

297346
const hasAnyPermission = React.useMemo(() => {
298347
if (!(treeName in TREE_RESOURCES)) return false;
299348
return (
300-
hasPermission(TREE_RESOURCES[treeName as TreeNameKey], 'rebuild_full_names') ||
301-
hasPermission(TREE_RESOURCES[treeName as TreeNameKey], 'repair')
349+
hasPermission(
350+
TREE_RESOURCES[treeName as TreeNameKey],
351+
'rebuild_full_names'
352+
) || hasPermission(TREE_RESOURCES[treeName as TreeNameKey], 'repair')
302353
);
303354
}, [treeName]);
304355

305356
const updatePosition = React.useCallback(() => {
306357
const element = anchorRef.current;
307358
if (!element) return;
308359
const rect = element.getBoundingClientRect();
309-
setPosition({ top: rect.bottom + window.scrollY + 4, left: rect.right + window.scrollX });
360+
setPosition({
361+
top: rect.bottom + window.scrollY + 4,
362+
left: rect.right + window.scrollX,
363+
});
310364
}, []);
311365

312366
React.useEffect(() => {
313367
if (!open) return;
314368
updatePosition();
315369
const onClick = (e: MouseEvent): void => {
316370
if (
317-
anchorRef.current?.contains(e.target as Node) ||
371+
anchorRef.current?.contains(e.target as Node) ||
318372
menuRef.current?.contains(e.target as Node)
319373
) {
320374
return; // Inside

0 commit comments

Comments
 (0)