From 760aea4d21c4464007690abe83ae8c4f9ceb474f Mon Sep 17 00:00:00 2001 From: Iwantexpresso Date: Tue, 12 May 2026 14:20:46 -0500 Subject: [PATCH 1/8] recalculates isresolved for each prep --- specifyweb/specify/api/calculated_fields.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/specifyweb/specify/api/calculated_fields.py b/specifyweb/specify/api/calculated_fields.py index e0693107325..99156d5f32a 100644 --- a/specifyweb/specify/api/calculated_fields.py +++ b/specifyweb/specify/api/calculated_fields.py @@ -114,6 +114,19 @@ def calculate_extra_fields(obj, data: dict[str, Any]) -> dict[str, Any]: preps = data["loanpreparations"] prep_count = len(preps) quantities = sum((prep.get('quantity') or 0) for prep in preps) + +#recalculate isresolved for each prep based on the current loan returns and resolved quantities + for prep in preps: + quantity_resolved = prep.get("quantityresolved") or 0 + quantity_returned = prep.get("quantityreturned") or 0 + total_quantity = prep.get("quantity") or 0 + + if quantity_resolved < total_quantity or quantity_returned < total_quantity: + prep["isresolved"] = False + else: + prep["isresolved"] = True + + unresolved_prep_count = sum(not prep["isresolved"] for prep in preps) unresolved_quantities = sum( max((prep.get("quantity") or 0) - (prep.get("quantityresolved") or 0), 0) From f69fd27f32db21c13c6260c03fd5a2af5c225db5 Mon Sep 17 00:00:00 2001 From: Iwantexpresso Date: Tue, 12 May 2026 19:47:29 +0000 Subject: [PATCH 2/8] Lint code with ESLint and Prettier Triggered by c89c44992dfb57194c3e119d070c70f61a188077 on branch refs/heads/issue-4883 --- .../components/DataModel/businessRuleDefs.ts | 14 ++++--- .../ExpressSearchConfigDialog.tsx | 4 +- .../ResultsOrderingTab.tsx | 28 +++++++++---- .../ExpressSearchConfigEditor.test.tsx | 41 ++++++++++--------- .../__tests__/RelatedTablesTab.test.tsx | 8 +++- .../__tests__/ResultsOrderingTab.test.tsx | 4 +- .../lib/components/FormCells/FormTable.tsx | 4 +- .../components/Header/ExpressSearchHooks.tsx | 15 ++++--- .../components/Header/ExpressSearchTask.tsx | 20 ++++----- .../Notifications/NotificationRenderers.tsx | 4 +- .../lib/components/WbPlanView/navigator.ts | 2 +- .../js_src/lib/localization/common.ts | 9 ++-- .../js_src/lib/localization/utils/config.ts | 2 +- .../js_src/lib/utils/schemaVisibility.ts | 2 +- 14 files changed, 87 insertions(+), 70 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts b/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts index 5b0fe4fe7d0..d8921c1df49 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts @@ -201,9 +201,10 @@ export const businessRuleDefs: MappedBusinessRuleDefs = { return undefined; }, catalogNumber: async (resource): Promise => { - const preferences = await import( - '../Preferences/collectionPreferences' - ).then(({ collectionPreferences }) => collectionPreferences); + const preferences = + await import('../Preferences/collectionPreferences').then( + ({ collectionPreferences }) => collectionPreferences + ); const uniqueCatalogNumberAccrossComponentAndCOPref = preferences.get( 'uniqueCatalogNumberAccrossComponentAndCO', @@ -429,9 +430,10 @@ export const businessRuleDefs: MappedBusinessRuleDefs = { return undefined; }, catalogNumber: async (resource): Promise => { - const preferences = await import( - '../Preferences/collectionPreferences' - ).then(({ collectionPreferences }) => collectionPreferences); + const preferences = + await import('../Preferences/collectionPreferences').then( + ({ collectionPreferences }) => collectionPreferences + ); const uniqueCatalogNumberAccrossComponentAndCOPref = preferences.get( 'uniqueCatalogNumberAccrossComponentAndCO', diff --git a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ExpressSearchConfigDialog.tsx b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ExpressSearchConfigDialog.tsx index 6c5b8a531b5..81cc07e0bd3 100644 --- a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ExpressSearchConfigDialog.tsx +++ b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ExpressSearchConfigDialog.tsx @@ -14,7 +14,7 @@ type ExpressSearchConfigDialogProps = { readonly isOpen: boolean; readonly onClose: () => void; readonly onSave?: () => void; -} +}; export function ExpressSearchConfigDialog({ isOpen, @@ -76,7 +76,7 @@ export function ExpressSearchConfigDialog({ isOpen={isOpen} onClose={onClose} > - diff --git a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ResultsOrderingTab.tsx b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ResultsOrderingTab.tsx index 4478fd66f5a..437e55abc1b 100644 --- a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ResultsOrderingTab.tsx +++ b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ResultsOrderingTab.tsx @@ -11,12 +11,17 @@ import { genericTables } from '../DataModel/tables'; function tableLabel(tableName: string): string { return ( - (genericTables[tableName as keyof typeof genericTables]?.label as string | undefined) ?? - camelToHuman(tableName) + (genericTables[tableName as keyof typeof genericTables]?.label as + | string + | undefined) ?? camelToHuman(tableName) ); } -export function ResultsOrderingTab({ config, relatedQueriesDefinitions = [], onChangeConfig }: any) { +export function ResultsOrderingTab({ + config, + relatedQueriesDefinitions = [], + onChangeConfig, +}: any) { const baseTables = config.tables .filter((t: any) => t.searchFields.some((sf: any) => sf.inUse !== false)) .map((t: any) => ({ @@ -29,8 +34,12 @@ export function ResultsOrderingTab({ config, relatedQueriesDefinitions = [], onC const activeQueries = config.relatedQueries .filter((rq: any) => rq.isActive) .map((rq: any) => { - const def = relatedQueriesDefinitions.find((def: any) => def.id === rq.id); - const title = def?.name ? getExpressSearchQueryTitle(def.name) : undefined; + const def = relatedQueriesDefinitions.find( + (def: any) => def.id === rq.id + ); + const title = def?.name + ? getExpressSearchQueryTitle(def.name) + : undefined; if (!def || !title || title === String(def.name)) { return undefined; @@ -86,7 +95,9 @@ export function ResultsOrderingTab({ config, relatedQueriesDefinitions = [], onC return (
-

{expressSearchConfigText.configureResultsOrdering()}

+

+ {expressSearchConfigText.configureResultsOrdering()} +

{expressSearchConfigText.reorderResultsOrderingDescription()}

@@ -99,7 +110,10 @@ export function ResultsOrderingTab({ config, relatedQueriesDefinitions = [], onC > {item.label}
- moveItem(index, 'up')}> + moveItem(index, 'up')} + > {icons.chevronUp} { @@ -65,42 +65,43 @@ describe('ExpressSearchConfigEditor', () => { }); expect(onChangeJSON).toHaveBeenCalled(); - const latestConfig = onChangeJSON.mock.calls[onChangeJSON.mock.calls.length - 1][0]; + const latestConfig = + onChangeJSON.mock.calls[onChangeJSON.mock.calls.length - 1][0]; expect(latestConfig.tables[0].tableName).toBe('Agent'); expect(latestConfig.tables[0].searchFields[0].fieldName).toBe('firstName'); }); test('renders loading state initially', async () => { const { getByText } = mount( - ); expect(getByText('Loading...')).toBeInTheDocument(); - + // Wait for it to finish loading to avoid act warnings await act(async () => { - await new Promise(resolve => setTimeout(resolve, 0)); + await new Promise((resolve) => setTimeout(resolve, 0)); }); }); test('renders tabs after data load', async () => { const { findByRole } = mount( - ); - + expect(await findByRole('tablist')).toBeInTheDocument(); }); test('switches tabs correctly', async () => { const { findByText, getByRole, user } = mount( - ); @@ -112,7 +113,7 @@ describe('ExpressSearchConfigEditor', () => { await act(async () => { await user.click(relatedTab); }); - + expect(await findByText('Related Tables Tab')).toBeInTheDocument(); // Click Results Ordering diff --git a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/RelatedTablesTab.test.tsx b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/RelatedTablesTab.test.tsx index bacba609689..e096a045a54 100644 --- a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/RelatedTablesTab.test.tsx +++ b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/RelatedTablesTab.test.tsx @@ -43,7 +43,9 @@ describe('RelatedTablesTab', () => { expect(onChangeConfig).toHaveBeenCalledTimes(1); const newConfig = onChangeConfig.mock.calls[0][0]; - expect(newConfig.relatedQueries.find((rq: any) => rq.id === '2').isActive).toBe(true); + expect( + newConfig.relatedQueries.find((rq: any) => rq.id === '2').isActive + ).toBe(true); const activeRow = rows[0]; const activeCheckbox = activeRow.querySelector('input[type="checkbox"]'); @@ -54,6 +56,8 @@ describe('RelatedTablesTab', () => { expect(onChangeConfig).toHaveBeenCalledTimes(2); const secondConfig = onChangeConfig.mock.calls[1][0]; - expect(secondConfig.relatedQueries.find((rq: any) => rq.id === '1').isActive).toBe(false); + expect( + secondConfig.relatedQueries.find((rq: any) => rq.id === '1').isActive + ).toBe(false); }); }); diff --git a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/ResultsOrderingTab.test.tsx b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/ResultsOrderingTab.test.tsx index 5997fc65f7c..4b15f595931 100644 --- a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/ResultsOrderingTab.test.tsx +++ b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/ResultsOrderingTab.test.tsx @@ -39,9 +39,7 @@ describe('ResultsOrderingTab', () => { displayFields: [], }, ], - relatedQueries: [ - { id: '8', isActive: true, displayOrder: 1 }, - ], + relatedQueries: [{ id: '8', isActive: true, displayOrder: 1 }], }; const onChangeConfig = jest.fn(); diff --git a/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx b/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx index 0eaae790141..baf13a48768 100644 --- a/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx @@ -214,8 +214,8 @@ export function FormTable({ resource.cid, Boolean( resource.specifyTable.name === 'Preparation' && - collectionPreparationPref && - resource.isNew() + collectionPreparationPref && + resource.isNew() ), ]) ) diff --git a/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchHooks.tsx b/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchHooks.tsx index a59d68078c7..62177d62253 100644 --- a/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchHooks.tsx +++ b/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchHooks.tsx @@ -52,14 +52,13 @@ export function usePrimarySearch( } async function fetchRelatedSearches(): Promise> { - return contextUnlockedPromise.then( - async (entrypoint) => - entrypoint === 'main' - ? ajax>('/context/available_related_searches.json', { - headers: { Accept: 'application/json' }, - cache: 'no-store', - }).then(({ data }) => data) - : foreverFetch>() + return contextUnlockedPromise.then(async (entrypoint) => + entrypoint === 'main' + ? ajax>('/context/available_related_searches.json', { + headers: { Accept: 'application/json' }, + cache: 'no-store', + }).then(({ data }) => data) + : foreverFetch>() ); } diff --git a/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchTask.tsx b/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchTask.tsx index 60686c9bccf..b392a409b6d 100644 --- a/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchTask.tsx +++ b/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchTask.tsx @@ -125,11 +125,7 @@ function ExpressSearchInstructions({ {headerText.documentation()} )} - +
    @@ -147,10 +143,8 @@ export function ExpressSearchView(): JSX.Element { const [pendingQuery] = value; const [isConfigOpen, setIsConfigOpen] = React.useState(false); const [configRefreshTrigger, setConfigRefreshTrigger] = React.useState(0); - const [showInstructions = true, setShowExpressSearchInstructions] = useCachedState( - 'expressSearch', - 'showSearchTips' - ); + const [showInstructions = true, setShowExpressSearchInstructions] = + useCachedState('expressSearch', 'showSearchTips'); const canEditExpressSearchConfig = hasToolPermission('resources', 'read') && hasToolPermission('resources', 'create') && @@ -176,11 +170,15 @@ export function ExpressSearchView(): JSX.Element { setShowExpressSearchInstructions((value) => !value)} + onClick={(): void => + setShowExpressSearchInstructions((value) => !value) + } /> {showInstructions && ( - setShowExpressSearchInstructions(false)} /> + setShowExpressSearchInstructions(false)} + /> )}
    setQuery(pendingQuery)}>
    diff --git a/specifyweb/frontend/js_src/lib/components/Notifications/NotificationRenderers.tsx b/specifyweb/frontend/js_src/lib/components/Notifications/NotificationRenderers.tsx index 26f4696c407..790b3373284 100644 --- a/specifyweb/frontend/js_src/lib/components/Notifications/NotificationRenderers.tsx +++ b/specifyweb/frontend/js_src/lib/components/Notifications/NotificationRenderers.tsx @@ -395,9 +395,7 @@ export const notificationRenderers: IR< ); }, 'collection-creation-starting'() { - return ( -

    {setupToolText.collectionCreationStarted()}

    - ); + return

    {setupToolText.collectionCreationStarted()}

    ; }, default(notification) { console.error('Unknown notification type', { notification }); diff --git a/specifyweb/frontend/js_src/lib/components/WbPlanView/navigator.ts b/specifyweb/frontend/js_src/lib/components/WbPlanView/navigator.ts index fca97e9c6be..fbc359e53b4 100644 --- a/specifyweb/frontend/js_src/lib/components/WbPlanView/navigator.ts +++ b/specifyweb/frontend/js_src/lib/components/WbPlanView/navigator.ts @@ -713,4 +713,4 @@ export function getMappingLineData({ : filtered.filter( ({ customSelectSubtype }) => customSelectSubtype !== 'tree' ); -} \ No newline at end of file +} diff --git a/specifyweb/frontend/js_src/lib/localization/common.ts b/specifyweb/frontend/js_src/lib/localization/common.ts index 6ad133cdc01..d75f8bb7f83 100644 --- a/specifyweb/frontend/js_src/lib/localization/common.ts +++ b/specifyweb/frontend/js_src/lib/localization/common.ts @@ -104,13 +104,16 @@ export const commonText = createDictionary({ 'hr-hr': 'Savjeti za pretraživanje', }, expressSearchInstructions: { - 'en-us': 'Separate multiple search terms with spaces, use % anywhere, * at the beginning or end, and wrap terms in quotes for exact multi-word matches.', + 'en-us': + 'Separate multiple search terms with spaces, use % anywhere, * at the beginning or end, and wrap terms in quotes for exact multi-word matches.', }, expressSearchDateFormats: { - 'en-us': 'Dates can be searched using either the YYYY-MM-DD or MM/DD/YYYY format.', + 'en-us': + 'Dates can be searched using either the YYYY-MM-DD or MM/DD/YYYY format.', }, expressSearchPhraseExample: { - 'en-us': 'To search a term with spaces, wrap the phrase in quotes, for example "Clinton Lake".', + 'en-us': + 'To search a term with spaces, wrap the phrase in quotes, for example "Clinton Lake".', }, apply: { 'en-us': 'Apply', diff --git a/specifyweb/frontend/js_src/lib/localization/utils/config.ts b/specifyweb/frontend/js_src/lib/localization/utils/config.ts index e358f61952f..b70b574e01a 100644 --- a/specifyweb/frontend/js_src/lib/localization/utils/config.ts +++ b/specifyweb/frontend/js_src/lib/localization/utils/config.ts @@ -24,7 +24,7 @@ export const languageCodeMapper = { 'de-ch': 'de_CH', 'pt-br': 'pt_BR', 'hr-hr': 'hr', - 'nb': 'nb_NO' + nb: 'nb_NO', } as const; export const languages = Object.keys(languageCodeMapper); diff --git a/specifyweb/frontend/js_src/lib/utils/schemaVisibility.ts b/specifyweb/frontend/js_src/lib/utils/schemaVisibility.ts index fe41c0b752e..9c483b4168b 100644 --- a/specifyweb/frontend/js_src/lib/utils/schemaVisibility.ts +++ b/specifyweb/frontend/js_src/lib/utils/schemaVisibility.ts @@ -5,4 +5,4 @@ export function isSchemaFieldVisible( defaultFieldName?: string ): boolean { return showHiddenFields || !isHidden || fieldName === defaultFieldName; -} \ No newline at end of file +} From 1c217d935dde8e4e02682964d07bbc1c04d8290d Mon Sep 17 00:00:00 2001 From: Iwantexpresso Date: Tue, 12 May 2026 15:11:28 -0500 Subject: [PATCH 3/8] fixed error inside cogpreps caused by some fiends being streated as string instead of ints --- specifyweb/backend/interactions/cog_preps.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/specifyweb/backend/interactions/cog_preps.py b/specifyweb/backend/interactions/cog_preps.py index 63089be38da..199843fede6 100644 --- a/specifyweb/backend/interactions/cog_preps.py +++ b/specifyweb/backend/interactions/cog_preps.py @@ -546,10 +546,11 @@ def modify_update_of_loan_return_sibling_preps(original_interaction_obj, updated in updated_interaction_data["loanpreparations"][loan_prep_idx].keys() else [] ) + #fixed error caused by string values being summed to ints total_quantity_returned = sum( - [loan_return["quantityreturned"] for loan_return in loan_return_data]) + [int(loan_return["quantityreturned"]) for loan_return in loan_return_data]) total_quantity_resolved = sum( - [loan_return["quantityresolved"] for loan_return in loan_return_data]) + [int(loan_return["quantityresolved"]) for loan_return in loan_return_data]) updated_interaction_data["loanpreparations"][loan_prep_idx]["quantityresolved"] = total_quantity_resolved updated_interaction_data["loanpreparations"][loan_prep_idx]["quantityreturned"] = total_quantity_returned From ef57a52abd10d8f41df503aecf51d688ea042807 Mon Sep 17 00:00:00 2001 From: Iwantexpresso Date: Tue, 12 May 2026 16:11:14 -0500 Subject: [PATCH 4/8] addoed comment to casting fix --- specifyweb/backend/interactions/cog_preps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/backend/interactions/cog_preps.py b/specifyweb/backend/interactions/cog_preps.py index 199843fede6..115cafed5cf 100644 --- a/specifyweb/backend/interactions/cog_preps.py +++ b/specifyweb/backend/interactions/cog_preps.py @@ -546,7 +546,7 @@ def modify_update_of_loan_return_sibling_preps(original_interaction_obj, updated in updated_interaction_data["loanpreparations"][loan_prep_idx].keys() else [] ) - #fixed error caused by string values being summed to ints + #fixed error firefox specific error caused by quantity returned being posted as a string instead of an int in the API request, need to convert to int before summing total_quantity_returned = sum( [int(loan_return["quantityreturned"]) for loan_return in loan_return_data]) total_quantity_resolved = sum( From a174c864f5c41ca52b51637f3d2a108d8ff084f5 Mon Sep 17 00:00:00 2001 From: Iwantexpresso Date: Wed, 13 May 2026 14:13:24 -0500 Subject: [PATCH 5/8] backend test failure attempted fix --- specifyweb/specify/api/calculated_fields.py | 27 +++++++++++++-------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/specifyweb/specify/api/calculated_fields.py b/specifyweb/specify/api/calculated_fields.py index 99156d5f32a..d242331c13a 100644 --- a/specifyweb/specify/api/calculated_fields.py +++ b/specifyweb/specify/api/calculated_fields.py @@ -17,6 +17,7 @@ Specifyuser, Collectionobject, Loan, + Loanpreparation, Deaccession, Accession, Collectionobjectgroupjoin, @@ -110,21 +111,27 @@ def calculate_extra_fields(obj, data: dict[str, Any]) -> dict[str, Any]: extra["isMemberOfCOG"] = Collectionobjectgroupjoin.objects.filter(childco=obj).exists() + + + elif isinstance(obj, Loanpreparation): # ← Add this case + quantity_resolved = obj.quantityresolved or 0 + quantity_returned = obj.quantityreturned or 0 + total_quantity = obj.quantity or 0 + + # Calculate isresolved - DON'T modify obj, just return the value + is_resolved = ( + quantity_resolved >= total_quantity and + quantity_returned >= total_quantity + ) + + extra['isresolved'] = is_resolved + + elif isinstance(obj, Loan): preps = data["loanpreparations"] prep_count = len(preps) quantities = sum((prep.get('quantity') or 0) for prep in preps) -#recalculate isresolved for each prep based on the current loan returns and resolved quantities - for prep in preps: - quantity_resolved = prep.get("quantityresolved") or 0 - quantity_returned = prep.get("quantityreturned") or 0 - total_quantity = prep.get("quantity") or 0 - - if quantity_resolved < total_quantity or quantity_returned < total_quantity: - prep["isresolved"] = False - else: - prep["isresolved"] = True unresolved_prep_count = sum(not prep["isresolved"] for prep in preps) From c3b13bf67a05059646b299e84b4ba71bf0ace2c4 Mon Sep 17 00:00:00 2001 From: Iwantexpresso Date: Mon, 18 May 2026 11:21:03 -0500 Subject: [PATCH 6/8] Revert "Merge branch 'issue-4883' of https://github.com/specify/specify7 into issue-4883" This reverts commit 57b81358f08b7aa187404a54f5f29b52cca7bd13, reversing changes made to 1c217d935dde8e4e02682964d07bbc1c04d8290d. --- .../components/DataModel/businessRuleDefs.ts | 14 +++---- .../ExpressSearchConfigDialog.tsx | 4 +- .../ResultsOrderingTab.tsx | 28 ++++--------- .../ExpressSearchConfigEditor.test.tsx | 41 +++++++++---------- .../__tests__/RelatedTablesTab.test.tsx | 8 +--- .../__tests__/ResultsOrderingTab.test.tsx | 4 +- .../lib/components/FormCells/FormTable.tsx | 4 +- .../components/Header/ExpressSearchHooks.tsx | 15 +++---- .../components/Header/ExpressSearchTask.tsx | 20 +++++---- .../Notifications/NotificationRenderers.tsx | 4 +- .../lib/components/WbPlanView/navigator.ts | 2 +- .../js_src/lib/localization/common.ts | 9 ++-- .../js_src/lib/localization/utils/config.ts | 2 +- .../js_src/lib/utils/schemaVisibility.ts | 2 +- 14 files changed, 70 insertions(+), 87 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts b/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts index d8921c1df49..5b0fe4fe7d0 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts @@ -201,10 +201,9 @@ export const businessRuleDefs: MappedBusinessRuleDefs = { return undefined; }, catalogNumber: async (resource): Promise => { - const preferences = - await import('../Preferences/collectionPreferences').then( - ({ collectionPreferences }) => collectionPreferences - ); + const preferences = await import( + '../Preferences/collectionPreferences' + ).then(({ collectionPreferences }) => collectionPreferences); const uniqueCatalogNumberAccrossComponentAndCOPref = preferences.get( 'uniqueCatalogNumberAccrossComponentAndCO', @@ -430,10 +429,9 @@ export const businessRuleDefs: MappedBusinessRuleDefs = { return undefined; }, catalogNumber: async (resource): Promise => { - const preferences = - await import('../Preferences/collectionPreferences').then( - ({ collectionPreferences }) => collectionPreferences - ); + const preferences = await import( + '../Preferences/collectionPreferences' + ).then(({ collectionPreferences }) => collectionPreferences); const uniqueCatalogNumberAccrossComponentAndCOPref = preferences.get( 'uniqueCatalogNumberAccrossComponentAndCO', diff --git a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ExpressSearchConfigDialog.tsx b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ExpressSearchConfigDialog.tsx index 81cc07e0bd3..6c5b8a531b5 100644 --- a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ExpressSearchConfigDialog.tsx +++ b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ExpressSearchConfigDialog.tsx @@ -14,7 +14,7 @@ type ExpressSearchConfigDialogProps = { readonly isOpen: boolean; readonly onClose: () => void; readonly onSave?: () => void; -}; +} export function ExpressSearchConfigDialog({ isOpen, @@ -76,7 +76,7 @@ export function ExpressSearchConfigDialog({ isOpen={isOpen} onClose={onClose} > - diff --git a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ResultsOrderingTab.tsx b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ResultsOrderingTab.tsx index 437e55abc1b..4478fd66f5a 100644 --- a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ResultsOrderingTab.tsx +++ b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ResultsOrderingTab.tsx @@ -11,17 +11,12 @@ import { genericTables } from '../DataModel/tables'; function tableLabel(tableName: string): string { return ( - (genericTables[tableName as keyof typeof genericTables]?.label as - | string - | undefined) ?? camelToHuman(tableName) + (genericTables[tableName as keyof typeof genericTables]?.label as string | undefined) ?? + camelToHuman(tableName) ); } -export function ResultsOrderingTab({ - config, - relatedQueriesDefinitions = [], - onChangeConfig, -}: any) { +export function ResultsOrderingTab({ config, relatedQueriesDefinitions = [], onChangeConfig }: any) { const baseTables = config.tables .filter((t: any) => t.searchFields.some((sf: any) => sf.inUse !== false)) .map((t: any) => ({ @@ -34,12 +29,8 @@ export function ResultsOrderingTab({ const activeQueries = config.relatedQueries .filter((rq: any) => rq.isActive) .map((rq: any) => { - const def = relatedQueriesDefinitions.find( - (def: any) => def.id === rq.id - ); - const title = def?.name - ? getExpressSearchQueryTitle(def.name) - : undefined; + const def = relatedQueriesDefinitions.find((def: any) => def.id === rq.id); + const title = def?.name ? getExpressSearchQueryTitle(def.name) : undefined; if (!def || !title || title === String(def.name)) { return undefined; @@ -95,9 +86,7 @@ export function ResultsOrderingTab({ return (
    -

    - {expressSearchConfigText.configureResultsOrdering()} -

    +

    {expressSearchConfigText.configureResultsOrdering()}

    {expressSearchConfigText.reorderResultsOrderingDescription()}

    @@ -110,10 +99,7 @@ export function ResultsOrderingTab({ > {item.label}
    - moveItem(index, 'up')} - > + moveItem(index, 'up')}> {icons.chevronUp} { @@ -65,43 +65,42 @@ describe('ExpressSearchConfigEditor', () => { }); expect(onChangeJSON).toHaveBeenCalled(); - const latestConfig = - onChangeJSON.mock.calls[onChangeJSON.mock.calls.length - 1][0]; + const latestConfig = onChangeJSON.mock.calls[onChangeJSON.mock.calls.length - 1][0]; expect(latestConfig.tables[0].tableName).toBe('Agent'); expect(latestConfig.tables[0].searchFields[0].fieldName).toBe('firstName'); }); test('renders loading state initially', async () => { const { getByText } = mount( - ); expect(getByText('Loading...')).toBeInTheDocument(); - + // Wait for it to finish loading to avoid act warnings await act(async () => { - await new Promise((resolve) => setTimeout(resolve, 0)); + await new Promise(resolve => setTimeout(resolve, 0)); }); }); test('renders tabs after data load', async () => { const { findByRole } = mount( - ); - + expect(await findByRole('tablist')).toBeInTheDocument(); }); test('switches tabs correctly', async () => { const { findByText, getByRole, user } = mount( - ); @@ -113,7 +112,7 @@ describe('ExpressSearchConfigEditor', () => { await act(async () => { await user.click(relatedTab); }); - + expect(await findByText('Related Tables Tab')).toBeInTheDocument(); // Click Results Ordering diff --git a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/RelatedTablesTab.test.tsx b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/RelatedTablesTab.test.tsx index e096a045a54..bacba609689 100644 --- a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/RelatedTablesTab.test.tsx +++ b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/RelatedTablesTab.test.tsx @@ -43,9 +43,7 @@ describe('RelatedTablesTab', () => { expect(onChangeConfig).toHaveBeenCalledTimes(1); const newConfig = onChangeConfig.mock.calls[0][0]; - expect( - newConfig.relatedQueries.find((rq: any) => rq.id === '2').isActive - ).toBe(true); + expect(newConfig.relatedQueries.find((rq: any) => rq.id === '2').isActive).toBe(true); const activeRow = rows[0]; const activeCheckbox = activeRow.querySelector('input[type="checkbox"]'); @@ -56,8 +54,6 @@ describe('RelatedTablesTab', () => { expect(onChangeConfig).toHaveBeenCalledTimes(2); const secondConfig = onChangeConfig.mock.calls[1][0]; - expect( - secondConfig.relatedQueries.find((rq: any) => rq.id === '1').isActive - ).toBe(false); + expect(secondConfig.relatedQueries.find((rq: any) => rq.id === '1').isActive).toBe(false); }); }); diff --git a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/ResultsOrderingTab.test.tsx b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/ResultsOrderingTab.test.tsx index 4b15f595931..5997fc65f7c 100644 --- a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/ResultsOrderingTab.test.tsx +++ b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/ResultsOrderingTab.test.tsx @@ -39,7 +39,9 @@ describe('ResultsOrderingTab', () => { displayFields: [], }, ], - relatedQueries: [{ id: '8', isActive: true, displayOrder: 1 }], + relatedQueries: [ + { id: '8', isActive: true, displayOrder: 1 }, + ], }; const onChangeConfig = jest.fn(); diff --git a/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx b/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx index baf13a48768..0eaae790141 100644 --- a/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx @@ -214,8 +214,8 @@ export function FormTable({ resource.cid, Boolean( resource.specifyTable.name === 'Preparation' && - collectionPreparationPref && - resource.isNew() + collectionPreparationPref && + resource.isNew() ), ]) ) diff --git a/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchHooks.tsx b/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchHooks.tsx index 62177d62253..a59d68078c7 100644 --- a/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchHooks.tsx +++ b/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchHooks.tsx @@ -52,13 +52,14 @@ export function usePrimarySearch( } async function fetchRelatedSearches(): Promise> { - return contextUnlockedPromise.then(async (entrypoint) => - entrypoint === 'main' - ? ajax>('/context/available_related_searches.json', { - headers: { Accept: 'application/json' }, - cache: 'no-store', - }).then(({ data }) => data) - : foreverFetch>() + return contextUnlockedPromise.then( + async (entrypoint) => + entrypoint === 'main' + ? ajax>('/context/available_related_searches.json', { + headers: { Accept: 'application/json' }, + cache: 'no-store', + }).then(({ data }) => data) + : foreverFetch>() ); } diff --git a/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchTask.tsx b/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchTask.tsx index b392a409b6d..60686c9bccf 100644 --- a/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchTask.tsx +++ b/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchTask.tsx @@ -125,7 +125,11 @@ function ExpressSearchInstructions({ {headerText.documentation()} )} - +
      @@ -143,8 +147,10 @@ export function ExpressSearchView(): JSX.Element { const [pendingQuery] = value; const [isConfigOpen, setIsConfigOpen] = React.useState(false); const [configRefreshTrigger, setConfigRefreshTrigger] = React.useState(0); - const [showInstructions = true, setShowExpressSearchInstructions] = - useCachedState('expressSearch', 'showSearchTips'); + const [showInstructions = true, setShowExpressSearchInstructions] = useCachedState( + 'expressSearch', + 'showSearchTips' + ); const canEditExpressSearchConfig = hasToolPermission('resources', 'read') && hasToolPermission('resources', 'create') && @@ -170,15 +176,11 @@ export function ExpressSearchView(): JSX.Element { - setShowExpressSearchInstructions((value) => !value) - } + onClick={(): void => setShowExpressSearchInstructions((value) => !value)} />
    {showInstructions && ( - setShowExpressSearchInstructions(false)} - /> + setShowExpressSearchInstructions(false)} /> )} setQuery(pendingQuery)}>
    diff --git a/specifyweb/frontend/js_src/lib/components/Notifications/NotificationRenderers.tsx b/specifyweb/frontend/js_src/lib/components/Notifications/NotificationRenderers.tsx index 790b3373284..26f4696c407 100644 --- a/specifyweb/frontend/js_src/lib/components/Notifications/NotificationRenderers.tsx +++ b/specifyweb/frontend/js_src/lib/components/Notifications/NotificationRenderers.tsx @@ -395,7 +395,9 @@ export const notificationRenderers: IR< ); }, 'collection-creation-starting'() { - return

    {setupToolText.collectionCreationStarted()}

    ; + return ( +

    {setupToolText.collectionCreationStarted()}

    + ); }, default(notification) { console.error('Unknown notification type', { notification }); diff --git a/specifyweb/frontend/js_src/lib/components/WbPlanView/navigator.ts b/specifyweb/frontend/js_src/lib/components/WbPlanView/navigator.ts index fbc359e53b4..fca97e9c6be 100644 --- a/specifyweb/frontend/js_src/lib/components/WbPlanView/navigator.ts +++ b/specifyweb/frontend/js_src/lib/components/WbPlanView/navigator.ts @@ -713,4 +713,4 @@ export function getMappingLineData({ : filtered.filter( ({ customSelectSubtype }) => customSelectSubtype !== 'tree' ); -} +} \ No newline at end of file diff --git a/specifyweb/frontend/js_src/lib/localization/common.ts b/specifyweb/frontend/js_src/lib/localization/common.ts index d75f8bb7f83..6ad133cdc01 100644 --- a/specifyweb/frontend/js_src/lib/localization/common.ts +++ b/specifyweb/frontend/js_src/lib/localization/common.ts @@ -104,16 +104,13 @@ export const commonText = createDictionary({ 'hr-hr': 'Savjeti za pretraživanje', }, expressSearchInstructions: { - 'en-us': - 'Separate multiple search terms with spaces, use % anywhere, * at the beginning or end, and wrap terms in quotes for exact multi-word matches.', + 'en-us': 'Separate multiple search terms with spaces, use % anywhere, * at the beginning or end, and wrap terms in quotes for exact multi-word matches.', }, expressSearchDateFormats: { - 'en-us': - 'Dates can be searched using either the YYYY-MM-DD or MM/DD/YYYY format.', + 'en-us': 'Dates can be searched using either the YYYY-MM-DD or MM/DD/YYYY format.', }, expressSearchPhraseExample: { - 'en-us': - 'To search a term with spaces, wrap the phrase in quotes, for example "Clinton Lake".', + 'en-us': 'To search a term with spaces, wrap the phrase in quotes, for example "Clinton Lake".', }, apply: { 'en-us': 'Apply', diff --git a/specifyweb/frontend/js_src/lib/localization/utils/config.ts b/specifyweb/frontend/js_src/lib/localization/utils/config.ts index b70b574e01a..e358f61952f 100644 --- a/specifyweb/frontend/js_src/lib/localization/utils/config.ts +++ b/specifyweb/frontend/js_src/lib/localization/utils/config.ts @@ -24,7 +24,7 @@ export const languageCodeMapper = { 'de-ch': 'de_CH', 'pt-br': 'pt_BR', 'hr-hr': 'hr', - nb: 'nb_NO', + 'nb': 'nb_NO' } as const; export const languages = Object.keys(languageCodeMapper); diff --git a/specifyweb/frontend/js_src/lib/utils/schemaVisibility.ts b/specifyweb/frontend/js_src/lib/utils/schemaVisibility.ts index 9c483b4168b..fe41c0b752e 100644 --- a/specifyweb/frontend/js_src/lib/utils/schemaVisibility.ts +++ b/specifyweb/frontend/js_src/lib/utils/schemaVisibility.ts @@ -5,4 +5,4 @@ export function isSchemaFieldVisible( defaultFieldName?: string ): boolean { return showHiddenFields || !isHidden || fieldName === defaultFieldName; -} +} \ No newline at end of file From 9b4d54926e9384a2fff28da692658016a859594c Mon Sep 17 00:00:00 2001 From: Iwantexpresso Date: Mon, 18 May 2026 16:24:01 +0000 Subject: [PATCH 7/8] Lint code with ESLint and Prettier Triggered by c3b13bf67a05059646b299e84b4ba71bf0ace2c4 on branch refs/heads/issue-4883 --- .../ExpressSearchConfigDialog.tsx | 4 +- .../ResultsOrderingTab.tsx | 28 +++++++++---- .../ExpressSearchConfigEditor.test.tsx | 41 ++++++++++--------- .../__tests__/RelatedTablesTab.test.tsx | 8 +++- .../__tests__/ResultsOrderingTab.test.tsx | 4 +- .../lib/components/FormCells/FormTable.tsx | 4 +- .../components/Header/ExpressSearchHooks.tsx | 15 ++++--- .../components/Header/ExpressSearchTask.tsx | 20 ++++----- .../Notifications/NotificationRenderers.tsx | 4 +- .../lib/components/WbPlanView/navigator.ts | 2 +- .../js_src/lib/localization/common.ts | 9 ++-- .../js_src/lib/localization/utils/config.ts | 2 +- .../js_src/lib/utils/schemaVisibility.ts | 2 +- 13 files changed, 79 insertions(+), 64 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ExpressSearchConfigDialog.tsx b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ExpressSearchConfigDialog.tsx index 6c5b8a531b5..81cc07e0bd3 100644 --- a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ExpressSearchConfigDialog.tsx +++ b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ExpressSearchConfigDialog.tsx @@ -14,7 +14,7 @@ type ExpressSearchConfigDialogProps = { readonly isOpen: boolean; readonly onClose: () => void; readonly onSave?: () => void; -} +}; export function ExpressSearchConfigDialog({ isOpen, @@ -76,7 +76,7 @@ export function ExpressSearchConfigDialog({ isOpen={isOpen} onClose={onClose} > - diff --git a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ResultsOrderingTab.tsx b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ResultsOrderingTab.tsx index 4478fd66f5a..437e55abc1b 100644 --- a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ResultsOrderingTab.tsx +++ b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/ResultsOrderingTab.tsx @@ -11,12 +11,17 @@ import { genericTables } from '../DataModel/tables'; function tableLabel(tableName: string): string { return ( - (genericTables[tableName as keyof typeof genericTables]?.label as string | undefined) ?? - camelToHuman(tableName) + (genericTables[tableName as keyof typeof genericTables]?.label as + | string + | undefined) ?? camelToHuman(tableName) ); } -export function ResultsOrderingTab({ config, relatedQueriesDefinitions = [], onChangeConfig }: any) { +export function ResultsOrderingTab({ + config, + relatedQueriesDefinitions = [], + onChangeConfig, +}: any) { const baseTables = config.tables .filter((t: any) => t.searchFields.some((sf: any) => sf.inUse !== false)) .map((t: any) => ({ @@ -29,8 +34,12 @@ export function ResultsOrderingTab({ config, relatedQueriesDefinitions = [], onC const activeQueries = config.relatedQueries .filter((rq: any) => rq.isActive) .map((rq: any) => { - const def = relatedQueriesDefinitions.find((def: any) => def.id === rq.id); - const title = def?.name ? getExpressSearchQueryTitle(def.name) : undefined; + const def = relatedQueriesDefinitions.find( + (def: any) => def.id === rq.id + ); + const title = def?.name + ? getExpressSearchQueryTitle(def.name) + : undefined; if (!def || !title || title === String(def.name)) { return undefined; @@ -86,7 +95,9 @@ export function ResultsOrderingTab({ config, relatedQueriesDefinitions = [], onC return (
    -

    {expressSearchConfigText.configureResultsOrdering()}

    +

    + {expressSearchConfigText.configureResultsOrdering()} +

    {expressSearchConfigText.reorderResultsOrderingDescription()}

    @@ -99,7 +110,10 @@ export function ResultsOrderingTab({ config, relatedQueriesDefinitions = [], onC > {item.label}
    - moveItem(index, 'up')}> + moveItem(index, 'up')} + > {icons.chevronUp} { @@ -65,42 +65,43 @@ describe('ExpressSearchConfigEditor', () => { }); expect(onChangeJSON).toHaveBeenCalled(); - const latestConfig = onChangeJSON.mock.calls[onChangeJSON.mock.calls.length - 1][0]; + const latestConfig = + onChangeJSON.mock.calls[onChangeJSON.mock.calls.length - 1][0]; expect(latestConfig.tables[0].tableName).toBe('Agent'); expect(latestConfig.tables[0].searchFields[0].fieldName).toBe('firstName'); }); test('renders loading state initially', async () => { const { getByText } = mount( - ); expect(getByText('Loading...')).toBeInTheDocument(); - + // Wait for it to finish loading to avoid act warnings await act(async () => { - await new Promise(resolve => setTimeout(resolve, 0)); + await new Promise((resolve) => setTimeout(resolve, 0)); }); }); test('renders tabs after data load', async () => { const { findByRole } = mount( - ); - + expect(await findByRole('tablist')).toBeInTheDocument(); }); test('switches tabs correctly', async () => { const { findByText, getByRole, user } = mount( - ); @@ -112,7 +113,7 @@ describe('ExpressSearchConfigEditor', () => { await act(async () => { await user.click(relatedTab); }); - + expect(await findByText('Related Tables Tab')).toBeInTheDocument(); // Click Results Ordering diff --git a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/RelatedTablesTab.test.tsx b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/RelatedTablesTab.test.tsx index bacba609689..e096a045a54 100644 --- a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/RelatedTablesTab.test.tsx +++ b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/RelatedTablesTab.test.tsx @@ -43,7 +43,9 @@ describe('RelatedTablesTab', () => { expect(onChangeConfig).toHaveBeenCalledTimes(1); const newConfig = onChangeConfig.mock.calls[0][0]; - expect(newConfig.relatedQueries.find((rq: any) => rq.id === '2').isActive).toBe(true); + expect( + newConfig.relatedQueries.find((rq: any) => rq.id === '2').isActive + ).toBe(true); const activeRow = rows[0]; const activeCheckbox = activeRow.querySelector('input[type="checkbox"]'); @@ -54,6 +56,8 @@ describe('RelatedTablesTab', () => { expect(onChangeConfig).toHaveBeenCalledTimes(2); const secondConfig = onChangeConfig.mock.calls[1][0]; - expect(secondConfig.relatedQueries.find((rq: any) => rq.id === '1').isActive).toBe(false); + expect( + secondConfig.relatedQueries.find((rq: any) => rq.id === '1').isActive + ).toBe(false); }); }); diff --git a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/ResultsOrderingTab.test.tsx b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/ResultsOrderingTab.test.tsx index 5997fc65f7c..4b15f595931 100644 --- a/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/ResultsOrderingTab.test.tsx +++ b/specifyweb/frontend/js_src/lib/components/ExpressSearchConfig/__tests__/ResultsOrderingTab.test.tsx @@ -39,9 +39,7 @@ describe('ResultsOrderingTab', () => { displayFields: [], }, ], - relatedQueries: [ - { id: '8', isActive: true, displayOrder: 1 }, - ], + relatedQueries: [{ id: '8', isActive: true, displayOrder: 1 }], }; const onChangeConfig = jest.fn(); diff --git a/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx b/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx index 0eaae790141..baf13a48768 100644 --- a/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx @@ -214,8 +214,8 @@ export function FormTable({ resource.cid, Boolean( resource.specifyTable.name === 'Preparation' && - collectionPreparationPref && - resource.isNew() + collectionPreparationPref && + resource.isNew() ), ]) ) diff --git a/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchHooks.tsx b/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchHooks.tsx index a59d68078c7..62177d62253 100644 --- a/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchHooks.tsx +++ b/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchHooks.tsx @@ -52,14 +52,13 @@ export function usePrimarySearch( } async function fetchRelatedSearches(): Promise> { - return contextUnlockedPromise.then( - async (entrypoint) => - entrypoint === 'main' - ? ajax>('/context/available_related_searches.json', { - headers: { Accept: 'application/json' }, - cache: 'no-store', - }).then(({ data }) => data) - : foreverFetch>() + return contextUnlockedPromise.then(async (entrypoint) => + entrypoint === 'main' + ? ajax>('/context/available_related_searches.json', { + headers: { Accept: 'application/json' }, + cache: 'no-store', + }).then(({ data }) => data) + : foreverFetch>() ); } diff --git a/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchTask.tsx b/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchTask.tsx index 60686c9bccf..b392a409b6d 100644 --- a/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchTask.tsx +++ b/specifyweb/frontend/js_src/lib/components/Header/ExpressSearchTask.tsx @@ -125,11 +125,7 @@ function ExpressSearchInstructions({ {headerText.documentation()} )} - +
      @@ -147,10 +143,8 @@ export function ExpressSearchView(): JSX.Element { const [pendingQuery] = value; const [isConfigOpen, setIsConfigOpen] = React.useState(false); const [configRefreshTrigger, setConfigRefreshTrigger] = React.useState(0); - const [showInstructions = true, setShowExpressSearchInstructions] = useCachedState( - 'expressSearch', - 'showSearchTips' - ); + const [showInstructions = true, setShowExpressSearchInstructions] = + useCachedState('expressSearch', 'showSearchTips'); const canEditExpressSearchConfig = hasToolPermission('resources', 'read') && hasToolPermission('resources', 'create') && @@ -176,11 +170,15 @@ export function ExpressSearchView(): JSX.Element { setShowExpressSearchInstructions((value) => !value)} + onClick={(): void => + setShowExpressSearchInstructions((value) => !value) + } />
    {showInstructions && ( - setShowExpressSearchInstructions(false)} /> + setShowExpressSearchInstructions(false)} + /> )} setQuery(pendingQuery)}>
    diff --git a/specifyweb/frontend/js_src/lib/components/Notifications/NotificationRenderers.tsx b/specifyweb/frontend/js_src/lib/components/Notifications/NotificationRenderers.tsx index 26f4696c407..790b3373284 100644 --- a/specifyweb/frontend/js_src/lib/components/Notifications/NotificationRenderers.tsx +++ b/specifyweb/frontend/js_src/lib/components/Notifications/NotificationRenderers.tsx @@ -395,9 +395,7 @@ export const notificationRenderers: IR< ); }, 'collection-creation-starting'() { - return ( -

    {setupToolText.collectionCreationStarted()}

    - ); + return

    {setupToolText.collectionCreationStarted()}

    ; }, default(notification) { console.error('Unknown notification type', { notification }); diff --git a/specifyweb/frontend/js_src/lib/components/WbPlanView/navigator.ts b/specifyweb/frontend/js_src/lib/components/WbPlanView/navigator.ts index fca97e9c6be..fbc359e53b4 100644 --- a/specifyweb/frontend/js_src/lib/components/WbPlanView/navigator.ts +++ b/specifyweb/frontend/js_src/lib/components/WbPlanView/navigator.ts @@ -713,4 +713,4 @@ export function getMappingLineData({ : filtered.filter( ({ customSelectSubtype }) => customSelectSubtype !== 'tree' ); -} \ No newline at end of file +} diff --git a/specifyweb/frontend/js_src/lib/localization/common.ts b/specifyweb/frontend/js_src/lib/localization/common.ts index 6ad133cdc01..d75f8bb7f83 100644 --- a/specifyweb/frontend/js_src/lib/localization/common.ts +++ b/specifyweb/frontend/js_src/lib/localization/common.ts @@ -104,13 +104,16 @@ export const commonText = createDictionary({ 'hr-hr': 'Savjeti za pretraživanje', }, expressSearchInstructions: { - 'en-us': 'Separate multiple search terms with spaces, use % anywhere, * at the beginning or end, and wrap terms in quotes for exact multi-word matches.', + 'en-us': + 'Separate multiple search terms with spaces, use % anywhere, * at the beginning or end, and wrap terms in quotes for exact multi-word matches.', }, expressSearchDateFormats: { - 'en-us': 'Dates can be searched using either the YYYY-MM-DD or MM/DD/YYYY format.', + 'en-us': + 'Dates can be searched using either the YYYY-MM-DD or MM/DD/YYYY format.', }, expressSearchPhraseExample: { - 'en-us': 'To search a term with spaces, wrap the phrase in quotes, for example "Clinton Lake".', + 'en-us': + 'To search a term with spaces, wrap the phrase in quotes, for example "Clinton Lake".', }, apply: { 'en-us': 'Apply', diff --git a/specifyweb/frontend/js_src/lib/localization/utils/config.ts b/specifyweb/frontend/js_src/lib/localization/utils/config.ts index e358f61952f..b70b574e01a 100644 --- a/specifyweb/frontend/js_src/lib/localization/utils/config.ts +++ b/specifyweb/frontend/js_src/lib/localization/utils/config.ts @@ -24,7 +24,7 @@ export const languageCodeMapper = { 'de-ch': 'de_CH', 'pt-br': 'pt_BR', 'hr-hr': 'hr', - 'nb': 'nb_NO' + nb: 'nb_NO', } as const; export const languages = Object.keys(languageCodeMapper); diff --git a/specifyweb/frontend/js_src/lib/utils/schemaVisibility.ts b/specifyweb/frontend/js_src/lib/utils/schemaVisibility.ts index fe41c0b752e..9c483b4168b 100644 --- a/specifyweb/frontend/js_src/lib/utils/schemaVisibility.ts +++ b/specifyweb/frontend/js_src/lib/utils/schemaVisibility.ts @@ -5,4 +5,4 @@ export function isSchemaFieldVisible( defaultFieldName?: string ): boolean { return showHiddenFields || !isHidden || fieldName === defaultFieldName; -} \ No newline at end of file +} From 8e6a174c388d31c49e3037f0158266901f068e98 Mon Sep 17 00:00:00 2001 From: Iwantexpresso Date: Mon, 18 May 2026 12:47:06 -0500 Subject: [PATCH 8/8] fixed backend failure, and created new business rules for Loan return preps --- .../businessrules/rules/Loanprep_rules.py | 56 +++++++++++++++++++ specifyweb/specify/api/calculated_fields.py | 18 ++---- 2 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 specifyweb/backend/businessrules/rules/Loanprep_rules.py diff --git a/specifyweb/backend/businessrules/rules/Loanprep_rules.py b/specifyweb/backend/businessrules/rules/Loanprep_rules.py new file mode 100644 index 00000000000..62fbcdfd8a0 --- /dev/null +++ b/specifyweb/backend/businessrules/rules/Loanprep_rules.py @@ -0,0 +1,56 @@ +import logging +from functools import wraps +from django.apps import apps + +logger = logging.getLogger(__name__) + +def orm_signal_handler(signal_name, model_name=None, dispatch_uid=None): + """Decorator for Django ORM signal handlers.""" + def decorator(rule): + @wraps(rule) + def handler(sender=None, instance=None, **kwargs): + # Handle both standard signal dispatch and edge cases + obj = instance or kwargs.get('instance') + + try: + rule(obj) + except Exception as e: + logger.exception(f"Error in {rule.__name__} for {obj.__class__.__name__}") + raise + + # connect the signal handler + try: + from django.db.models.signals import pre_save, post_save, pre_delete, post_delete + + signal_map = { + 'pre_save': pre_save, + 'post_save': post_save, + 'pre_delete': pre_delete, + 'post_delete': post_delete, + } + signal = signal_map.get(signal_name) + + if signal is None: + raise ValueError(f"Unknown signal: {signal_name}") + + if model_name is not None: + try: + model = apps.get_model('specify', model_name) + signal.connect( + handler, + sender=model, + dispatch_uid=dispatch_uid or f"{rule.__module__}.{rule.__name__}" + ) + except LookupError: + logger.debug(f"Model {model_name} not found, skipping signal handler registration") + else: + signal.connect( + handler, + dispatch_uid=dispatch_uid or f"{rule.__module__}.{rule.__name__}" + ) + except Exception as e: + logger.exception(f"Failed to register signal handler {rule.__name__}") + + return handler + + return decorator \ No newline at end of file diff --git a/specifyweb/specify/api/calculated_fields.py b/specifyweb/specify/api/calculated_fields.py index d242331c13a..c4c37943cbf 100644 --- a/specifyweb/specify/api/calculated_fields.py +++ b/specifyweb/specify/api/calculated_fields.py @@ -113,18 +113,12 @@ def calculate_extra_fields(obj, data: dict[str, Any]) -> dict[str, Any]: - elif isinstance(obj, Loanpreparation): # ← Add this case - quantity_resolved = obj.quantityresolved or 0 - quantity_returned = obj.quantityreturned or 0 - total_quantity = obj.quantity or 0 - - # Calculate isresolved - DON'T modify obj, just return the value - is_resolved = ( - quantity_resolved >= total_quantity and - quantity_returned >= total_quantity - ) - - extra['isresolved'] = is_resolved + elif isinstance(obj, Loanpreparation): + # calculate the resolved status based on quantity and quantityresolved, used to update IsResolved thorugh a new business rule + quantity_resolved = data.get('quantityresolved') or 0 + total_quantity = data.get('quantity') or 0 + is_resolved = (quantity_resolved >= total_quantity) + extra['isresolved'] = is_resolved elif isinstance(obj, Loan):