From 9ee255da4c56c003f2b4a89c5367ea9adc8314c9 Mon Sep 17 00:00:00 2001 From: Jesper Hodge Date: Mon, 13 Apr 2026 14:33:11 -0400 Subject: [PATCH 1/5] fix: input align and count indicator --- src/taxonomy/tag-list/UsageCountDisplay.tsx | 30 +++++++++++++++++++++ src/taxonomy/tree-table/CreateRow.tsx | 17 ++++++++++-- src/taxonomy/tree-table/NestedRows.tsx | 3 ++- src/taxonomy/tree-table/TableBody.tsx | 5 ++-- src/taxonomy/tree-table/TableView.tsx | 2 +- 5 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 src/taxonomy/tag-list/UsageCountDisplay.tsx diff --git a/src/taxonomy/tag-list/UsageCountDisplay.tsx b/src/taxonomy/tag-list/UsageCountDisplay.tsx new file mode 100644 index 0000000000..f938c5e2e3 --- /dev/null +++ b/src/taxonomy/tag-list/UsageCountDisplay.tsx @@ -0,0 +1,30 @@ +import { + Bubble, +} from '@openedx/paragon'; +import type { Row } from '@tanstack/react-table'; +import type { + TreeRowData, +} from '../tree-table/types'; + +interface TagListRowData extends TreeRowData { + depth: number; + childCount: number; + usageCount?: number; + isNew?: boolean; + isEditing?: boolean; +} + +const asTagListRowData = (row: Row): TagListRowData => ( + row.original as unknown as TagListRowData +); + +export const UsageCountDisplay = ({ row }: { row: Row }) => { + const count = asTagListRowData(row).usageCount ?? 0; + return ( + count > 0 && ( + + {count} + + ) + ); +}; diff --git a/src/taxonomy/tree-table/CreateRow.tsx b/src/taxonomy/tree-table/CreateRow.tsx index 4091db0a56..7b06bdba96 100644 --- a/src/taxonomy/tree-table/CreateRow.tsx +++ b/src/taxonomy/tree-table/CreateRow.tsx @@ -5,6 +5,8 @@ import { useIntl } from '@edx/frontend-platform/i18n'; import { EditableCell } from './EditableCell'; import type { CreateRowMutationState, TreeColumnDef } from './types'; import messages from './messages'; +import { Row } from '@tanstack/react-table'; +import { UsageCountDisplay } from '../tag-list/UsageCountDisplay'; interface DraftRowProps { draftError: string; @@ -18,6 +20,7 @@ interface DraftRowProps { requireValueChangeToEnableSave?: boolean; rowTestId?: string; rowId?: string; + row?: any; } interface CreateRowProps { @@ -42,6 +45,7 @@ interface EditRowProps { columns: TreeColumnDef[]; indent?: number; validate: (value: string, mode?: 'soft' | 'hard') => boolean; + row: any; } const DraftRow: React.FC = ({ @@ -56,6 +60,8 @@ const DraftRow: React.FC = ({ requireValueChangeToEnableSave = false, rowTestId, rowId, + row, + }) => { const [rowValue, setRowValue] = useState(initialValue); const [saveDisabled, setSaveDisabled] = useState(true); @@ -91,10 +97,12 @@ const DraftRow: React.FC = ({ } }; + const indentClass = indent > 0 ? `tree-table-indent tree-table-indent-${indent}` : ''; + return ( -
+
= ({ />
+ + {row ? : null} + @@ -181,6 +192,7 @@ const EditRow: React.FC = ({ columns, indent = 0, validate, + row, }) => { const handleCancel = () => { setDraftError(''); @@ -198,6 +210,7 @@ const EditRow: React.FC = ({ indent={indent} validate={validate} requireValueChangeToEnableSave + row={row} /> ); }; diff --git a/src/taxonomy/tree-table/NestedRows.tsx b/src/taxonomy/tree-table/NestedRows.tsx index 216bcfbf18..3f15497a55 100644 --- a/src/taxonomy/tree-table/NestedRows.tsx +++ b/src/taxonomy/tree-table/NestedRows.tsx @@ -126,6 +126,7 @@ const NestedRows = ({ columns={columns} indent={indent} validate={validate} + row={row} /> ) : ( @@ -137,7 +138,7 @@ const NestedRows = ({ return ( {isFirstColumn ? (
{content}
diff --git a/src/taxonomy/tree-table/TableBody.tsx b/src/taxonomy/tree-table/TableBody.tsx index 6e1819a289..f0b5f9ee24 100644 --- a/src/taxonomy/tree-table/TableBody.tsx +++ b/src/taxonomy/tree-table/TableBody.tsx @@ -106,12 +106,13 @@ const TableBody = ({ updateRowMutation={updateRowMutation} columns={columns} validate={validate} + row={row} /> ) : ( {row.getVisibleCells() - .map((cell, index) => ( - + .map((cell) => ( + {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} diff --git a/src/taxonomy/tree-table/TableView.tsx b/src/taxonomy/tree-table/TableView.tsx index 79aa625259..2608dd0f03 100644 --- a/src/taxonomy/tree-table/TableView.tsx +++ b/src/taxonomy/tree-table/TableView.tsx @@ -142,7 +142,7 @@ const TableView = ({ {headerGroup.headers.map((header, index) => ( {header.isPlaceholder ? null From 68e4e1885d9e6f43ec144a605c3107ba4370f53c Mon Sep 17 00:00:00 2001 From: Jesper Hodge Date: Mon, 13 Apr 2026 15:10:20 -0400 Subject: [PATCH 2/5] fix: counts & save button align --- src/taxonomy/tag-list/UsageCountDisplay.tsx | 13 +- src/taxonomy/tag-list/tagColumns.tsx | 22 +-- src/taxonomy/tag-list/types.ts | 9 + src/taxonomy/tree-table/CreateRow.test.tsx | 2 +- src/taxonomy/tree-table/CreateRow.tsx | 180 +------------------- src/taxonomy/tree-table/DraftRow.tsx | 126 ++++++++++++++ src/taxonomy/tree-table/EditRow.tsx | 49 ++++++ 7 files changed, 195 insertions(+), 206 deletions(-) create mode 100644 src/taxonomy/tag-list/types.ts create mode 100644 src/taxonomy/tree-table/DraftRow.tsx create mode 100644 src/taxonomy/tree-table/EditRow.tsx diff --git a/src/taxonomy/tag-list/UsageCountDisplay.tsx b/src/taxonomy/tag-list/UsageCountDisplay.tsx index f938c5e2e3..cb468a49bd 100644 --- a/src/taxonomy/tag-list/UsageCountDisplay.tsx +++ b/src/taxonomy/tag-list/UsageCountDisplay.tsx @@ -5,20 +5,13 @@ import type { Row } from '@tanstack/react-table'; import type { TreeRowData, } from '../tree-table/types'; - -interface TagListRowData extends TreeRowData { - depth: number; - childCount: number; - usageCount?: number; - isNew?: boolean; - isEditing?: boolean; -} +import { TagListRowData } from './types'; const asTagListRowData = (row: Row): TagListRowData => ( row.original as unknown as TagListRowData ); -export const UsageCountDisplay = ({ row }: { row: Row }) => { +const UsageCountDisplay = ({ row }: { row: Row }) => { const count = asTagListRowData(row).usageCount ?? 0; return ( count > 0 && ( @@ -28,3 +21,5 @@ export const UsageCountDisplay = ({ row }: { row: Row }) => { ) ); }; + +export default UsageCountDisplay; diff --git a/src/taxonomy/tag-list/tagColumns.tsx b/src/taxonomy/tag-list/tagColumns.tsx index 77ebc2341d..8639d2167e 100644 --- a/src/taxonomy/tag-list/tagColumns.tsx +++ b/src/taxonomy/tag-list/tagColumns.tsx @@ -1,5 +1,4 @@ import { - Bubble, Icon, IconButton, IconButtonWithTooltip, @@ -18,18 +17,12 @@ import type { TreeColumnDef, TreeRowData, } from '../tree-table/types'; +import { TagListRowData } from './types'; import OptionalExpandLink from './OptionalExpandLink'; +import UsageCountDisplay from './UsageCountDisplay'; const EDITABLE_COLUMNS = ['value']; -interface TagListRowData extends TreeRowData { - depth: number; - childCount: number; - usageCount?: number; - isNew?: boolean; - isEditing?: boolean; -} - const asTagListRowData = (row: Row): TagListRowData => ( row.original as unknown as TagListRowData ); @@ -49,17 +42,6 @@ interface GetColumnsArgs { maxDepth: number; } -const UsageCountDisplay = ({ row }: { row: Row }) => { - const count = asTagListRowData(row).usageCount ?? 0; - return ( - count > 0 && ( - - {count} - - ) - ); -}; - interface ActionsHeaderProps { onStartDraft: () => void; setDraftError: (error: string) => void; diff --git a/src/taxonomy/tag-list/types.ts b/src/taxonomy/tag-list/types.ts new file mode 100644 index 0000000000..8cdb1208ae --- /dev/null +++ b/src/taxonomy/tag-list/types.ts @@ -0,0 +1,9 @@ +import { TreeRowData } from '../tree-table/types'; + +export interface TagListRowData extends TreeRowData { + depth: number; + childCount: number; + usageCount?: number; + isNew?: boolean; + isEditing?: boolean; +} diff --git a/src/taxonomy/tree-table/CreateRow.test.tsx b/src/taxonomy/tree-table/CreateRow.test.tsx index dedfb12490..1302e87e25 100644 --- a/src/taxonomy/tree-table/CreateRow.test.tsx +++ b/src/taxonomy/tree-table/CreateRow.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { IntlProvider } from '@edx/frontend-platform/i18n'; import { fireEvent, render, screen } from '@testing-library/react'; -import { CreateRow } from './CreateRow'; +import CreateRow from './CreateRow'; const wrapper = ({ children }: { children: React.ReactNode }) => ( {children} diff --git a/src/taxonomy/tree-table/CreateRow.tsx b/src/taxonomy/tree-table/CreateRow.tsx index 7b06bdba96..f130560b02 100644 --- a/src/taxonomy/tree-table/CreateRow.tsx +++ b/src/taxonomy/tree-table/CreateRow.tsx @@ -1,27 +1,6 @@ -import React, { useState } from 'react'; -import { Button, Spinner } from '@openedx/paragon'; -import { useIntl } from '@edx/frontend-platform/i18n'; - -import { EditableCell } from './EditableCell'; -import type { CreateRowMutationState, TreeColumnDef } from './types'; -import messages from './messages'; -import { Row } from '@tanstack/react-table'; -import { UsageCountDisplay } from '../tag-list/UsageCountDisplay'; - -interface DraftRowProps { - draftError: string; - initialValue?: string; - onSave: (value: string) => void; - onCancel: () => void; - mutationState: CreateRowMutationState; - columns: TreeColumnDef[]; - indent?: number; - validate: (value: string, mode?: 'soft' | 'hard') => boolean; - requireValueChangeToEnableSave?: boolean; - rowTestId?: string; - rowId?: string; - row?: any; -} +import React from 'react'; +import type { CreateRowMutationState } from './types'; +import DraftRow from './DraftRow'; interface CreateRowProps { draftError: string; @@ -30,126 +9,10 @@ interface CreateRowProps { setIsCreatingTopRow: (isCreating: boolean) => void; exitDraftWithoutSave: () => void; createRowMutation: CreateRowMutationState; - columns: TreeColumnDef[]; indent?: number; validate: (value: string, mode?: 'soft' | 'hard') => boolean; } -interface EditRowProps { - draftError: string; - setDraftError: (error: string) => void; - initialValue: string; - handleUpdateRow: (value: string) => void; - cancelEditRow: () => void; - updateRowMutation: CreateRowMutationState; - columns: TreeColumnDef[]; - indent?: number; - validate: (value: string, mode?: 'soft' | 'hard') => boolean; - row: any; -} - -const DraftRow: React.FC = ({ - draftError, - initialValue = '', - onSave, - onCancel, - mutationState, - columns, - indent = 0, - validate, - requireValueChangeToEnableSave = false, - rowTestId, - rowId, - row, - -}) => { - const [rowValue, setRowValue] = useState(initialValue); - const [saveDisabled, setSaveDisabled] = useState(true); - const intl = useIntl(); - - const updateSaveDisabled = (value: string) => { - const trimmedValue = value.trim(); - const isValid = validate(value, 'soft'); - const isUnchanged = requireValueChangeToEnableSave && trimmedValue === initialValue.trim(); - setSaveDisabled(!isValid || !trimmedValue || isUnchanged || mutationState.isPending || false); - }; - - const handleValueChange = (e: React.ChangeEvent) => { - const { value } = e.target; - setRowValue(value); - updateSaveDisabled(value); - }; - - const handleSave = () => { - onSave(rowValue.trim()); - }; - - const handleValueCellKeyPress = (e: React.KeyboardEvent) => { - if (e.key === 'Enter' && !saveDisabled && !draftError) { - e.preventDefault(); - handleSave(); - return; - } - - if (e.key === 'Escape') { - e.preventDefault(); - onCancel(); - } - }; - - const indentClass = indent > 0 ? `tree-table-indent tree-table-indent-${indent}` : ''; - - return ( - - -
- -
- - - {row ? : null} - - - - - - - - - - {mutationState.isPending && ( - - )} - - - - ); -}; - const CreateRow: React.FC = ({ draftError, setDraftError, @@ -157,7 +20,6 @@ const CreateRow: React.FC = ({ setIsCreatingTopRow, exitDraftWithoutSave, createRowMutation, - columns, indent = 0, validate, }) => { @@ -173,7 +35,6 @@ const CreateRow: React.FC = ({ onSave={handleCreateRow} onCancel={handleCancel} mutationState={createRowMutation} - columns={columns} indent={indent} validate={validate} rowId="creating-top-row" @@ -182,37 +43,4 @@ const CreateRow: React.FC = ({ ); }; -const EditRow: React.FC = ({ - draftError, - setDraftError, - initialValue, - handleUpdateRow, - cancelEditRow, - updateRowMutation, - columns, - indent = 0, - validate, - row, -}) => { - const handleCancel = () => { - setDraftError(''); - cancelEditRow(); - }; - - return ( - - ); -}; - -export { CreateRow, EditRow }; +export default CreateRow; diff --git a/src/taxonomy/tree-table/DraftRow.tsx b/src/taxonomy/tree-table/DraftRow.tsx new file mode 100644 index 0000000000..e5ed747071 --- /dev/null +++ b/src/taxonomy/tree-table/DraftRow.tsx @@ -0,0 +1,126 @@ +import React, { useState } from 'react'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { Button, Spinner } from '@openedx/paragon'; +import { Row } from '@tanstack/react-table'; + +import { EditableCell } from './EditableCell'; +import type { CreateRowMutationState, TreeRowData } from './types'; +import messages from './messages'; +import UsageCountDisplay from '../tag-list/UsageCountDisplay'; + +interface DraftRowProps { + draftError: string; + initialValue?: string; + onSave: (value: string) => void; + onCancel: () => void; + mutationState: CreateRowMutationState; + indent?: number; + validate: (value: string, mode?: 'soft' | 'hard') => boolean; + requireValueChangeToEnableSave?: boolean; + rowTestId?: string; + rowId?: string; + row?: Row; +} + +const DraftRow: React.FC = ({ + draftError, + initialValue = '', + onSave, + onCancel, + mutationState, + indent = 0, + validate, + requireValueChangeToEnableSave = false, + rowTestId, + rowId, + row, + +}) => { + const [rowValue, setRowValue] = useState(initialValue); + const [saveDisabled, setSaveDisabled] = useState(true); + const intl = useIntl(); + + const updateSaveDisabled = (value: string) => { + const trimmedValue = value.trim(); + const isValid = validate(value, 'soft'); + const isUnchanged = requireValueChangeToEnableSave && trimmedValue === initialValue.trim(); + setSaveDisabled(!isValid || !trimmedValue || isUnchanged || mutationState.isPending || false); + }; + + const handleValueChange = (e: React.ChangeEvent) => { + const { value } = e.target; + setRowValue(value); + updateSaveDisabled(value); + }; + + const handleSave = () => { + onSave(rowValue.trim()); + }; + + const handleValueCellKeyPress = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !saveDisabled && !draftError) { + e.preventDefault(); + handleSave(); + return; + } + + if (e.key === 'Escape') { + e.preventDefault(); + onCancel(); + } + }; + + const indentClass = indent > 0 ? `tree-table-indent tree-table-indent-${indent}` : ''; + + return ( + + +
+ +
+ + + {row ? : null} + + + + + + + + + + {mutationState.isPending && ( + + )} + + + + ); +}; + +export default DraftRow; diff --git a/src/taxonomy/tree-table/EditRow.tsx b/src/taxonomy/tree-table/EditRow.tsx new file mode 100644 index 0000000000..12a83fa055 --- /dev/null +++ b/src/taxonomy/tree-table/EditRow.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { Row } from '@tanstack/react-table'; +import type { CreateRowMutationState, TreeRowData } from './types'; +import DraftRow from './DraftRow'; + +interface EditRowProps { + draftError: string; + setDraftError: (error: string) => void; + initialValue: string; + handleUpdateRow: (value: string) => void; + cancelEditRow: () => void; + updateRowMutation: CreateRowMutationState; + indent?: number; + validate: (value: string, mode?: 'soft' | 'hard') => boolean; + row: Row; +} + +const EditRow: React.FC = ({ + draftError, + setDraftError, + initialValue, + handleUpdateRow, + cancelEditRow, + updateRowMutation, + indent = 0, + validate, + row, +}) => { + const handleCancel = () => { + setDraftError(''); + cancelEditRow(); + }; + + return ( + + ); +}; + +export default EditRow; From 1c0759467e2ff2036dac282cafed1035d68681e4 Mon Sep 17 00:00:00 2001 From: Jesper Hodge Date: Mon, 13 Apr 2026 16:34:37 -0400 Subject: [PATCH 3/5] fix: error message --- src/messages.ts | 4 +++ src/taxonomy/data/apiHooks.ts | 25 +++++----------- src/taxonomy/tag-list/hooks.ts | 34 ++++++++++++++++++---- src/taxonomy/tree-table/CreateRow.test.tsx | 1 - src/taxonomy/tree-table/NestedRows.tsx | 7 ++--- src/taxonomy/tree-table/TableBody.tsx | 5 ++-- 6 files changed, 45 insertions(+), 31 deletions(-) diff --git a/src/messages.ts b/src/messages.ts index 1620914b5b..06f7ffd519 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -10,6 +10,10 @@ const messages = defineMessages({ id: 'authoring.alert.support.text', defaultMessage: 'Support Page', }, + unknownError: { + id: 'authoring.alert.error.unknown', + defaultMessage: 'Unknown error', + }, }); export default messages; diff --git a/src/taxonomy/data/apiHooks.ts b/src/taxonomy/data/apiHooks.ts index 479cc2c4db..96c8020090 100644 --- a/src/taxonomy/data/apiHooks.ts +++ b/src/taxonomy/data/apiHooks.ts @@ -214,18 +214,13 @@ export const useSubTags = (taxonomyId: number, parentTagValue: string) => useQue export const useCreateTag = (taxonomyId: number) => { const queryClient = useQueryClient(); - const intl = useIntl(); return useMutation({ mutationFn: async ({ value, parentTagValue }: { value: string, parentTagValue?: string }) => { - try { - await getAuthenticatedHttpClient().post( - apiUrls.createTag(taxonomyId), - { tag: value, parent_tag_value: parentTagValue }, - ); - } catch (err) { - throw new Error(getApiErrorMessage(err, intl)); - } + await getAuthenticatedHttpClient().post( + apiUrls.createTag(taxonomyId), + { tag: value, parent_tag_value: parentTagValue }, + ); }, onSuccess: () => { queryClient.invalidateQueries({ @@ -246,14 +241,10 @@ export const useUpdateTag = (taxonomyId: number) => { return useMutation({ mutationFn: async ({ value, originalValue }: { value: string, originalValue: string }) => { - try { - await getAuthenticatedHttpClient().patch( - apiUrls.updateTag(taxonomyId), - { tag: originalValue, updated_tag_value: value }, - ); - } catch (err) { - throw new Error(getApiErrorMessage(err)); - } + await getAuthenticatedHttpClient().patch( + apiUrls.updateTag(taxonomyId), + { tag: originalValue, updated_tag_value: value }, + ); }, onSuccess: () => { queryClient.invalidateQueries({ diff --git a/src/taxonomy/tag-list/hooks.ts b/src/taxonomy/tag-list/hooks.ts index 5484d88b0e..19c61600c5 100644 --- a/src/taxonomy/tag-list/hooks.ts +++ b/src/taxonomy/tag-list/hooks.ts @@ -13,6 +13,8 @@ import { } from './constants'; import messages from './messages'; +import globalMessages from '../../messages'; +import { AxiosError } from 'axios'; /** Interface for table mode actions for React's `useReducer` hook. * @@ -163,6 +165,26 @@ const useEditActions = ({ return true; }; + const getErrorMessage = (error: unknown): string => { + let errorMessage: string = ''; + if (error instanceof AxiosError) { + const responseData = error.response?.data; + const tagError = Object.entries(responseData)?.find((errItem: [string, unknown]) => ( + ['tag', 'value', 'updated_tag_value'].includes(errItem[0].toLowerCase()) + )); + + const errorMessages = tagError ? tagError[1] : ( + (error as Error).message || intl.formatMessage(globalMessages.unknownError) + ); + errorMessage = Array.isArray(errorMessages) ? errorMessages.join('; ') : String(errorMessages); + } else { + errorMessage = (error as Error).message || intl.formatMessage(globalMessages.unknownError); + } + + errorMessage = errorMessage.replace(/\.$/, ''); // Remove trailing period for better message formatting + return errorMessage; + }; + const handleCreateTag = async (value: string, parentTagValue?: string) => { const trimmed = value.trim(); @@ -182,9 +204,9 @@ const useEditActions = ({ setIsCreatingTopTag(false); setCreatingParentId(null); } catch (error) { - const message = intl.formatMessage(messages.tagCreationErrorMessage, { errorMessage: (error as Error)?.message }); - setDraftError((error as Error)?.message || intl.formatMessage(messages.tagCreationErrorMessage, { errorMessage: '' })); - setToast({ show: true, message }); + const errorMessage = getErrorMessage(error); + setDraftError(errorMessage); + setToast({ show: true, message: intl.formatMessage(messages.tagCreationErrorMessage, { errorMessage }) }); } }; @@ -211,9 +233,9 @@ const useEditActions = ({ message: intl.formatMessage(messages.tagUpdateSuccessMessage, { name: trimmed }), }); } catch (error) { - const message = intl.formatMessage(messages.tagUpdateErrorMessage, { errorMessage: (error as Error)?.message }); - setDraftError((error as Error)?.message || intl.formatMessage(messages.tagUpdateErrorMessage, { errorMessage: '' })); - setToast({ show: true, message }); + const errorMessage = getErrorMessage(error); + setDraftError(errorMessage); + setToast({ show: true, message: intl.formatMessage(messages.tagUpdateErrorMessage, { errorMessage }) }); } }; diff --git a/src/taxonomy/tree-table/CreateRow.test.tsx b/src/taxonomy/tree-table/CreateRow.test.tsx index 1302e87e25..7510f3f3bb 100644 --- a/src/taxonomy/tree-table/CreateRow.test.tsx +++ b/src/taxonomy/tree-table/CreateRow.test.tsx @@ -15,7 +15,6 @@ const baseProps = () => ({ setIsCreatingTopRow: jest.fn(), exitDraftWithoutSave: jest.fn(), createRowMutation: { isPending: false }, - columns: [{ id: 'value' }], validate: jest.fn((value: string) => value.trim().length > 0), }); diff --git a/src/taxonomy/tree-table/NestedRows.tsx b/src/taxonomy/tree-table/NestedRows.tsx index 3f15497a55..a0c42b5db8 100644 --- a/src/taxonomy/tree-table/NestedRows.tsx +++ b/src/taxonomy/tree-table/NestedRows.tsx @@ -7,7 +7,8 @@ import type { TreeColumnDef, CreateRowMutationState, } from './types'; -import { CreateRow, EditRow } from './CreateRow'; +import CreateRow from './CreateRow'; +import EditRow from './EditRow'; interface NestedRowsProps { /** The parent row object from TanStack React Table */ @@ -103,7 +104,6 @@ const NestedRows = ({ setIsCreatingTopRow={setIsCreatingTopRow} exitDraftWithoutSave={onCancelCreation} createRowMutation={createRowMutation} - columns={[]} indent={indent} validate={validate} /> @@ -123,7 +123,6 @@ const NestedRows = ({ exitDraftWithoutSave(); }} updateRowMutation={updateRowMutation} - columns={columns} indent={indent} validate={validate} row={row} @@ -138,7 +137,7 @@ const NestedRows = ({ return ( {isFirstColumn ? (
{content}
diff --git a/src/taxonomy/tree-table/TableBody.tsx b/src/taxonomy/tree-table/TableBody.tsx index f0b5f9ee24..99fca02dd7 100644 --- a/src/taxonomy/tree-table/TableBody.tsx +++ b/src/taxonomy/tree-table/TableBody.tsx @@ -13,7 +13,8 @@ import type { TreeColumnDef, TreeTable, } from './types'; -import { CreateRow, EditRow } from './CreateRow'; +import CreateRow from './CreateRow'; +import EditRow from './EditRow'; interface TableBodyProps { columns: TreeColumnDef[]; @@ -86,7 +87,6 @@ const TableBody = ({ setIsCreatingTopRow={setIsCreatingTopRow} exitDraftWithoutSave={exitDraftWithoutSave} createRowMutation={createRowMutation} - columns={columns} validate={validate} /> )} @@ -104,7 +104,6 @@ const TableBody = ({ exitDraftWithoutSave(); }} updateRowMutation={updateRowMutation} - columns={columns} validate={validate} row={row} /> From 675a56665197e29d9e72e7db04d6cb761fbbd8f0 Mon Sep 17 00:00:00 2001 From: Jesper Hodge Date: Tue, 14 Apr 2026 10:30:41 -0400 Subject: [PATCH 4/5] fix: error message --- src/taxonomy/tree-table/SaveErrorAlert.tsx | 42 ++++++++++++++++++++++ src/taxonomy/tree-table/TableView.tsx | 14 ++------ 2 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 src/taxonomy/tree-table/SaveErrorAlert.tsx diff --git a/src/taxonomy/tree-table/SaveErrorAlert.tsx b/src/taxonomy/tree-table/SaveErrorAlert.tsx new file mode 100644 index 0000000000..d7749fa3c5 --- /dev/null +++ b/src/taxonomy/tree-table/SaveErrorAlert.tsx @@ -0,0 +1,42 @@ +import React, { useEffect } from 'react'; +import { + Alert, +} from '@openedx/paragon'; + +import { Info } from '@openedx/paragon/icons'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import './TableView.scss'; +import messages from './messages'; + +interface SaveErrorAlertProps { + draftError: string | undefined; + isError: boolean | undefined; + isUpdateError: boolean | undefined; +} +const SaveErrorAlert = ({ draftError, isError, isUpdateError }: SaveErrorAlertProps) => { + const intl = useIntl(); + const hasError = (isError || isUpdateError) && !!draftError; + const [alertOpen, setAlertOpen] = React.useState(hasError); + + useEffect(() => { + if (hasError) { + setAlertOpen(true); + } + if (!hasError) { + setAlertOpen(false); + } + }, [hasError, isError, isUpdateError, draftError]); + + if (!alertOpen) { return null; } + + return ( + { setAlertOpen(false) }}> + + {intl.formatMessage(messages.errorSavingTitle)} + + {intl.formatMessage(messages.errorSavingMessage, { errorMessage: draftError }) } + + ); +}; + +export default SaveErrorAlert; diff --git a/src/taxonomy/tree-table/TableView.tsx b/src/taxonomy/tree-table/TableView.tsx index 2608dd0f03..92ad3bd5fe 100644 --- a/src/taxonomy/tree-table/TableView.tsx +++ b/src/taxonomy/tree-table/TableView.tsx @@ -5,7 +5,6 @@ import { Card, ActionRow, Pagination, - Alert, Icon, } from '@openedx/paragon'; @@ -18,7 +17,7 @@ import { type PaginationState, } from '@tanstack/react-table'; -import { ArrowDropUpDown, Info } from '@openedx/paragon/icons'; +import { ArrowDropUpDown } from '@openedx/paragon/icons'; import { useIntl } from '@edx/frontend-platform/i18n'; import TableBody from './TableBody'; import './TableView.scss'; @@ -30,6 +29,7 @@ import type { TreeRowData, } from './types'; import messages from './messages'; +import SaveErrorAlert from './SaveErrorAlert'; interface TableViewProps { treeData: TreeRowData[]; @@ -102,18 +102,10 @@ const TableView = ({ const { isError } = createRowMutation; const { isError: isUpdateError } = updateRowMutation; - const [showError, setShowError] = React.useState(true); return ( <> - {(isError || isUpdateError) && showError && ( - setShowError(false)}> - - {intl.formatMessage(messages.errorSavingTitle)} - - {intl.formatMessage(messages.errorSavingMessage, { errorMessage: draftError || intl.formatMessage(messages.errorSavingMessage, { errorMessage: '' }) })} - - )} +
From ff20f6b507c4a8ab6866734b55c4c3dcfc320376 Mon Sep 17 00:00:00 2001 From: Jesper Hodge Date: Tue, 14 Apr 2026 12:12:23 -0400 Subject: [PATCH 5/5] fix: lint --- src/taxonomy/data/apiHooks.ts | 1 - src/taxonomy/tag-list/OptionalExpandLink.tsx | 2 +- src/taxonomy/tag-list/TagListTable.tsx | 4 ++-- src/taxonomy/tag-list/UsageCountDisplay.tsx | 2 +- src/taxonomy/tag-list/hooks.ts | 11 +++++------ src/taxonomy/tag-list/tagColumns.tsx | 4 ++-- src/taxonomy/tag-list/tagTree.ts | 2 +- src/taxonomy/tag-list/types.ts | 2 +- src/taxonomy/tree-table/DraftRow.tsx | 2 +- src/taxonomy/tree-table/EditableCell.tsx | 3 ++- src/taxonomy/tree-table/SaveErrorAlert.tsx | 2 +- 11 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/taxonomy/data/apiHooks.ts b/src/taxonomy/data/apiHooks.ts index 96c8020090..e0cd927598 100644 --- a/src/taxonomy/data/apiHooks.ts +++ b/src/taxonomy/data/apiHooks.ts @@ -13,7 +13,6 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { camelCaseObject } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { useIntl } from '@edx/frontend-platform/i18n'; import { apiUrls, ALL_TAXONOMIES, getApiErrorMessage } from './api'; import * as api from './api'; import type { QueryOptions, TagListData } from './types'; diff --git a/src/taxonomy/tag-list/OptionalExpandLink.tsx b/src/taxonomy/tag-list/OptionalExpandLink.tsx index edfa6580f2..e42c198dcb 100644 --- a/src/taxonomy/tag-list/OptionalExpandLink.tsx +++ b/src/taxonomy/tag-list/OptionalExpandLink.tsx @@ -4,7 +4,7 @@ import { ExpandLess, ExpandMore } from '@openedx/paragon/icons'; import { Row } from '@tanstack/react-table'; import { useIntl } from '@edx/frontend-platform/i18n'; -import type { TreeRowData } from '../tree-table/types'; +import type { TreeRowData } from '@src/taxonomy/tree-table/types'; import messages from './messages'; interface OptionalExpandLinkProps { diff --git a/src/taxonomy/tag-list/TagListTable.tsx b/src/taxonomy/tag-list/TagListTable.tsx index c9c80adb08..bd62f675e2 100644 --- a/src/taxonomy/tag-list/TagListTable.tsx +++ b/src/taxonomy/tag-list/TagListTable.tsx @@ -4,9 +4,9 @@ import React, { useEffect, } from 'react'; import type { PaginationState } from '@tanstack/react-table'; -import { useTagListData, useCreateTag, useUpdateTag } from '../data/apiHooks'; +import { TableView } from '@src/taxonomy/tree-table'; +import { useTagListData, useCreateTag, useUpdateTag } from '@src/taxonomy/data/apiHooks'; import { TagTree } from './tagTree'; -import { TableView } from '../tree-table'; import type { RowId, TreeColumnDef, diff --git a/src/taxonomy/tag-list/UsageCountDisplay.tsx b/src/taxonomy/tag-list/UsageCountDisplay.tsx index cb468a49bd..97a95ad026 100644 --- a/src/taxonomy/tag-list/UsageCountDisplay.tsx +++ b/src/taxonomy/tag-list/UsageCountDisplay.tsx @@ -4,7 +4,7 @@ import { import type { Row } from '@tanstack/react-table'; import type { TreeRowData, -} from '../tree-table/types'; +} from '@src/taxonomy/tree-table/types'; import { TagListRowData } from './types'; const asTagListRowData = (row: Row): TagListRowData => ( diff --git a/src/taxonomy/tag-list/hooks.ts b/src/taxonomy/tag-list/hooks.ts index 19c61600c5..8ced5f3a5b 100644 --- a/src/taxonomy/tag-list/hooks.ts +++ b/src/taxonomy/tag-list/hooks.ts @@ -1,10 +1,11 @@ import { useReducer } from 'react'; import { useIntl } from '@edx/frontend-platform/i18n'; -import { useCreateTag, useUpdateTag } from '../data/apiHooks'; +import globalMessages from '@src/messages'; +import { useCreateTag, useUpdateTag } from '@src/taxonomy/data/apiHooks'; +import type { RowId } from '@src/taxonomy/tree-table/types'; import { TagTree } from './tagTree'; import { TagListTableError } from './errors'; -import type { RowId } from '../tree-table/types'; import { TABLE_MODES, TRANSITION_TABLE, @@ -13,8 +14,6 @@ import { } from './constants'; import messages from './messages'; -import globalMessages from '../../messages'; -import { AxiosError } from 'axios'; /** Interface for table mode actions for React's `useReducer` hook. * @@ -165,9 +164,9 @@ const useEditActions = ({ return true; }; - const getErrorMessage = (error: unknown): string => { + const getErrorMessage = (error: any): string => { let errorMessage: string = ''; - if (error instanceof AxiosError) { + if (error.name === 'AxiosError') { const responseData = error.response?.data; const tagError = Object.entries(responseData)?.find((errItem: [string, unknown]) => ( ['tag', 'value', 'updated_tag_value'].includes(errItem[0].toLowerCase()) diff --git a/src/taxonomy/tag-list/tagColumns.tsx b/src/taxonomy/tag-list/tagColumns.tsx index 8639d2167e..363a1a9a12 100644 --- a/src/taxonomy/tag-list/tagColumns.tsx +++ b/src/taxonomy/tag-list/tagColumns.tsx @@ -11,13 +11,13 @@ import { import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import type { Row } from '@tanstack/react-table'; -import messages from './messages'; import type { RowId, TreeColumnDef, TreeRowData, -} from '../tree-table/types'; +} from '@src/taxonomy/tree-table/types'; import { TagListRowData } from './types'; +import messages from './messages'; import OptionalExpandLink from './OptionalExpandLink'; import UsageCountDisplay from './UsageCountDisplay'; diff --git a/src/taxonomy/tag-list/tagTree.ts b/src/taxonomy/tag-list/tagTree.ts index 06d43a04b6..f6cf9023c9 100644 --- a/src/taxonomy/tag-list/tagTree.ts +++ b/src/taxonomy/tag-list/tagTree.ts @@ -1,5 +1,5 @@ +import type { TagData } from '@src/taxonomy/data/types'; import { TagTreeError } from './errors'; -import type { TagData } from '../data/types'; export interface TagTreeNode extends TagData { subRows?: TagTreeNode[]; diff --git a/src/taxonomy/tag-list/types.ts b/src/taxonomy/tag-list/types.ts index 8cdb1208ae..845cee6956 100644 --- a/src/taxonomy/tag-list/types.ts +++ b/src/taxonomy/tag-list/types.ts @@ -1,4 +1,4 @@ -import { TreeRowData } from '../tree-table/types'; +import { TreeRowData } from '@src/taxonomy/tree-table/types'; export interface TagListRowData extends TreeRowData { depth: number; diff --git a/src/taxonomy/tree-table/DraftRow.tsx b/src/taxonomy/tree-table/DraftRow.tsx index e5ed747071..f15bc4f4a5 100644 --- a/src/taxonomy/tree-table/DraftRow.tsx +++ b/src/taxonomy/tree-table/DraftRow.tsx @@ -3,10 +3,10 @@ import { useIntl } from '@edx/frontend-platform/i18n'; import { Button, Spinner } from '@openedx/paragon'; import { Row } from '@tanstack/react-table'; +import UsageCountDisplay from '@src/taxonomy/tag-list/UsageCountDisplay'; import { EditableCell } from './EditableCell'; import type { CreateRowMutationState, TreeRowData } from './types'; import messages from './messages'; -import UsageCountDisplay from '../tag-list/UsageCountDisplay'; interface DraftRowProps { draftError: string; diff --git a/src/taxonomy/tree-table/EditableCell.tsx b/src/taxonomy/tree-table/EditableCell.tsx index 8cdc10aabc..d4de95fa74 100644 --- a/src/taxonomy/tree-table/EditableCell.tsx +++ b/src/taxonomy/tree-table/EditableCell.tsx @@ -7,8 +7,9 @@ import React, { import { Form } from '@openedx/paragon'; import { useIntl } from '@edx/frontend-platform/i18n'; + +import OptionalExpandLink from '@src/taxonomy/tag-list/OptionalExpandLink'; import messages from './messages'; -import OptionalExpandLink from '../tag-list/OptionalExpandLink'; /** * Props for the EditableCell component. diff --git a/src/taxonomy/tree-table/SaveErrorAlert.tsx b/src/taxonomy/tree-table/SaveErrorAlert.tsx index d7749fa3c5..4476e06c79 100644 --- a/src/taxonomy/tree-table/SaveErrorAlert.tsx +++ b/src/taxonomy/tree-table/SaveErrorAlert.tsx @@ -30,7 +30,7 @@ const SaveErrorAlert = ({ draftError, isError, isUpdateError }: SaveErrorAlertPr if (!alertOpen) { return null; } return ( - { setAlertOpen(false) }}> + { setAlertOpen(false); }}> {intl.formatMessage(messages.errorSavingTitle)}