diff --git a/src/frontend/src/components/forms/ApiForm.tsx b/src/frontend/src/components/forms/ApiForm.tsx index 9a3ca4a0b0..88eadd0ce2 100644 --- a/src/frontend/src/components/forms/ApiForm.tsx +++ b/src/frontend/src/components/forms/ApiForm.tsx @@ -35,6 +35,7 @@ import { } from '../../functions/forms'; import { invalidResponse } from '../../functions/notifications'; import { getDetailUrl } from '../../functions/urls'; +import { TableState } from '../../hooks/UseTable'; import { PathParams } from '../../states/ApiState'; import { Boundary } from '../Boundary'; import { @@ -54,6 +55,7 @@ export interface ApiFormAction { * Properties for the ApiForm component * @param url : The API endpoint to fetch the form data from * @param pk : Optional primary-key value when editing an existing object + * @param pk_field : Optional primary-key field name (default: pk) * @param pathParams : Optional path params for the url * @param method : Optional HTTP method to use when submitting the form (default: GET) * @param fields : The fields to render in the form @@ -67,10 +69,12 @@ export interface ApiFormAction { * @param onFormError : A callback function to call when the form is submitted with errors. * @param modelType : Define a model type for this form * @param follow : Boolean, follow the result of the form (if possible) + * @param table : Table to update on success (if provided) */ export interface ApiFormProps { url: ApiEndpoints | string; pk?: number | string | undefined; + pk_field?: string; pathParams?: PathParams; method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; fields?: ApiFormFieldSet; @@ -87,6 +91,7 @@ export interface ApiFormProps { successMessage?: string; onFormSuccess?: (data: any) => void; onFormError?: () => void; + table?: TableState; modelType?: ModelType; follow?: boolean; actions?: ApiFormAction[]; @@ -391,14 +396,22 @@ export function ApiForm({ case 204: // Form was submitted successfully - // Optionally call the onFormSuccess callback if (props.onFormSuccess) { + // A custom callback hook is provided props.onFormSuccess(response.data); } - if (props.follow) { - if (props.modelType && response.data?.pk) { - navigate(getDetailUrl(props.modelType, response.data?.pk)); + if (props.follow && props.modelType && response.data?.pk) { + // If we want to automatically follow the returned data + navigate(getDetailUrl(props.modelType, response.data?.pk)); + } else if (props.table) { + // If we want to automatically update or reload a linked table + let pk_field = props.pk_field ?? 'pk'; + + if (props.pk && response?.data[pk_field]) { + props.table.updateRecord(response.data); + } else { + props.table.refreshTable(); } } diff --git a/src/frontend/src/components/settings/SettingItem.tsx b/src/frontend/src/components/settings/SettingItem.tsx index dc14022998..292f5a1c19 100644 --- a/src/frontend/src/components/settings/SettingItem.tsx +++ b/src/frontend/src/components/settings/SettingItem.tsx @@ -1,4 +1,3 @@ -import { t } from '@lingui/macro'; import { Button, Group, @@ -9,103 +8,25 @@ import { Text, useMantineColorScheme } from '@mantine/core'; -import { showNotification } from '@mantine/notifications'; import { IconEdit } from '@tabler/icons-react'; import { useMemo } from 'react'; -import { api } from '../../App'; -import { ModelType } from '../../enums/ModelType'; -import { openModalApiForm } from '../../functions/forms'; -import { apiUrl } from '../../states/ApiState'; -import { SettingsStateProps } from '../../states/SettingsState'; -import { Setting, SettingType } from '../../states/states'; +import { Setting } from '../../states/states'; import { vars } from '../../theme'; -import { ApiFormFieldType } from '../forms/fields/ApiFormField'; +import { Boundary } from '../Boundary'; /** * Render a single setting value */ function SettingValue({ - settingsState, setting, - onChange + onEdit, + onToggle }: { - settingsState: SettingsStateProps; setting: Setting; - onChange?: () => void; + onEdit: (setting: Setting) => void; + onToggle: (setting: Setting, value: boolean) => void; }) { - // Callback function when a boolean value is changed - function onToggle(value: boolean) { - api - .patch( - apiUrl(settingsState.endpoint, setting.key, settingsState.pathParams), - { value: value } - ) - .then(() => { - showNotification({ - title: t`Setting updated`, - message: t`${setting?.name} updated successfully`, - color: 'green' - }); - settingsState.fetchSettings(); - onChange?.(); - }) - .catch((error) => { - showNotification({ - title: t`Error editing setting`, - message: error.message, - color: 'red' - }); - }); - } - - // Callback function to open the edit dialog (for non-boolean settings) - function onEditButton() { - const fieldDefinition: ApiFormFieldType = { - value: setting?.value ?? '', - field_type: setting?.type ?? 'string', - label: setting?.name, - description: setting?.description - }; - - // Match related field - if ( - fieldDefinition.field_type === SettingType.Model && - setting.api_url && - setting.model_name - ) { - fieldDefinition.api_url = setting.api_url; - - // TODO: improve this model matching mechanism - fieldDefinition.model = setting.model_name.split('.')[1] as ModelType; - } else if (setting.choices?.length > 0) { - // Match choices - fieldDefinition.field_type = SettingType.Choice; - fieldDefinition.choices = setting?.choices || []; - } - - openModalApiForm({ - url: settingsState.endpoint, - pk: setting.key, - pathParams: settingsState.pathParams, - method: 'PATCH', - title: t`Edit Setting`, - ignorePermissionCheck: true, - fields: { - value: fieldDefinition - }, - onFormSuccess() { - showNotification({ - title: t`Setting updated`, - message: t`${setting?.name} updated successfully`, - color: 'green' - }); - settingsState.fetchSettings(); - onChange?.(); - } - }); - } - // Determine the text to display for the setting value const valueText: string = useMemo(() => { let value = setting.value; @@ -130,7 +51,7 @@ function SettingValue({ size="sm" radius="lg" checked={setting.value.toLowerCase() == 'true'} - onChange={(event) => onToggle(event.currentTarget.checked)} + onChange={(event) => onToggle(setting, event.currentTarget.checked)} style={{ paddingRight: '20px' }} @@ -140,12 +61,12 @@ function SettingValue({ return valueText ? ( - ) : ( - ); @@ -156,15 +77,15 @@ function SettingValue({ * Display a single setting item, and allow editing of the value */ export function SettingItem({ - settingsState, setting, shaded, - onChange + onEdit, + onToggle }: { - settingsState: SettingsStateProps; setting: Setting; shaded: boolean; - onChange?: () => void; + onEdit: (setting: Setting) => void; + onToggle: (setting: Setting, value: boolean) => void; }) { const { colorScheme } = useMantineColorScheme(); @@ -184,11 +105,9 @@ export function SettingItem({ {setting.description} - + + + ); diff --git a/src/frontend/src/components/settings/SettingList.tsx b/src/frontend/src/components/settings/SettingList.tsx index bf404578cc..4cd0b4e3ad 100644 --- a/src/frontend/src/components/settings/SettingList.tsx +++ b/src/frontend/src/components/settings/SettingList.tsx @@ -1,8 +1,19 @@ -import { Trans } from '@lingui/macro'; +import { Trans, t } from '@lingui/macro'; import { Stack, Text } from '@mantine/core'; -import React, { useEffect, useMemo, useRef } from 'react'; +import { notifications } from '@mantine/notifications'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState +} from 'react'; import { useStore } from 'zustand'; +import { api } from '../../App'; +import { ModelType } from '../../enums/ModelType'; +import { useEditApiFormModal } from '../../hooks/UseForm'; +import { apiUrl } from '../../states/ApiState'; import { SettingsStateProps, createMachineSettingsState, @@ -10,6 +21,7 @@ import { useGlobalSettingsState, useUserSettingsState } from '../../states/SettingsState'; +import { Setting } from '../../states/states'; import { SettingItem } from './SettingItem'; /** @@ -33,8 +45,82 @@ export function SettingList({ [settingsState?.settings] ); + const [setting, setSetting] = useState(undefined); + + const editSettingModal = useEditApiFormModal({ + url: settingsState.endpoint, + pk: setting?.key, + pathParams: settingsState.pathParams, + title: t`Edit Setting`, + fields: { + value: { + value: setting?.value ?? '', + field_type: + setting?.type ?? (setting?.choices?.length ?? 0) > 0 + ? 'choice' + : 'string', + label: setting?.name, + description: setting?.description, + api_url: setting?.api_url ?? '', + model: (setting?.model_name?.split('.')[1] as ModelType) ?? null, + choices: setting?.choices ?? undefined + } + }, + successMessage: t`Setting ${setting?.key} updated successfully`, + onFormSuccess: () => { + settingsState.fetchSettings(); + onChange?.(); + } + }); + + // Callback for editing a single setting instance + const onValueEdit = useCallback( + (setting: Setting) => { + setSetting(setting); + editSettingModal.open(); + }, + [editSettingModal] + ); + + // Callback for toggling a single boolean setting instance + const onValueToggle = useCallback( + (setting: Setting, value: boolean) => { + api + .patch( + apiUrl(settingsState.endpoint, setting.key, settingsState.pathParams), + { + value: value + } + ) + .then(() => { + notifications.hide('setting'); + notifications.show({ + title: t`Setting updated`, + message: t`Setting ${setting.key} updated successfully`, + color: 'green', + id: 'setting' + }); + onChange?.(); + }) + .catch((error) => { + notifications.hide('setting'); + notifications.show({ + title: t`Error editing setting`, + message: error.message, + color: 'red', + id: 'setting' + }); + }) + .finally(() => { + settingsState.fetchSettings(); + }); + }, + [settingsState] + ); + return ( <> + {editSettingModal.modal} {(keys || allKeys).map((key, i) => { const setting = settingsState?.settings?.find( @@ -45,10 +131,10 @@ export function SettingList({ {setting ? ( ) : ( diff --git a/src/frontend/src/forms/CompanyForms.tsx b/src/frontend/src/forms/CompanyForms.tsx index 7073110004..2f71c89672 100644 --- a/src/frontend/src/forms/CompanyForms.tsx +++ b/src/frontend/src/forms/CompanyForms.tsx @@ -82,6 +82,9 @@ export function useManufacturerPartFields() { export function useManufacturerPartParameterFields() { return useMemo(() => { const fields: ApiFormFieldSet = { + manufacturer_part: { + disabled: true + }, name: {}, value: {}, units: {} diff --git a/src/frontend/src/pages/part/pricing/PriceBreakPanel.tsx b/src/frontend/src/pages/part/pricing/PriceBreakPanel.tsx index a82f837e41..8a8f87d5d0 100644 --- a/src/frontend/src/pages/part/pricing/PriceBreakPanel.tsx +++ b/src/frontend/src/pages/part/pricing/PriceBreakPanel.tsx @@ -84,9 +84,7 @@ export default function PriceBreakPanel({ url: tableUrl, pk: selectedPriceBreak, title: t`Delete Price Break`, - onFormSuccess: () => { - table.refreshTable(); - } + table: table }); const columns: TableColumn[] = useMemo(() => { diff --git a/src/frontend/src/tables/bom/BomTable.tsx b/src/frontend/src/tables/bom/BomTable.tsx index 5fa5ef2120..b2e5cf018f 100644 --- a/src/frontend/src/tables/bom/BomTable.tsx +++ b/src/frontend/src/tables/bom/BomTable.tsx @@ -312,7 +312,7 @@ export function BomTable({ part: partId }, successMessage: t`BOM item created`, - onFormSuccess: table.refreshTable + table: table }); const editBomItem = useEditApiFormModal({ @@ -321,7 +321,7 @@ export function BomTable({ title: t`Edit BOM Item`, fields: bomItemFields(), successMessage: t`BOM item updated`, - onFormSuccess: table.refreshTable + table: table }); const deleteBomItem = useDeleteApiFormModal({ @@ -329,7 +329,7 @@ export function BomTable({ pk: selectedBomItem, title: t`Delete BOM Item`, successMessage: t`BOM item deleted`, - onFormSuccess: table.refreshTable + table: table }); const rowActions = useCallback( diff --git a/src/frontend/src/tables/build/BuildOutputTable.tsx b/src/frontend/src/tables/build/BuildOutputTable.tsx index 8b1ec7b99c..1ea8403896 100644 --- a/src/frontend/src/tables/build/BuildOutputTable.tsx +++ b/src/frontend/src/tables/build/BuildOutputTable.tsx @@ -113,9 +113,7 @@ export default function BuildOutputTable({ build }: { build: any }) { url: apiUrl(ApiEndpoints.build_output_create, buildId), title: t`Add Build Output`, fields: buildOutputFields, - onFormSuccess: () => { - table.refreshTable(); - } + table: table }); const [selectedOutputs, setSelectedOutputs] = useState([]); diff --git a/src/frontend/src/tables/company/AddressTable.tsx b/src/frontend/src/tables/company/AddressTable.tsx index bef8d0cef9..f888e01dd3 100644 --- a/src/frontend/src/tables/company/AddressTable.tsx +++ b/src/frontend/src/tables/company/AddressTable.tsx @@ -124,7 +124,7 @@ export function AddressTable({ company: companyId }, successMessage: t`Address created`, - onFormSuccess: table.refreshTable + table: table }); const [selectedAddress, setSelectedAddress] = useState(-1); @@ -134,15 +134,15 @@ export function AddressTable({ pk: selectedAddress, title: t`Edit Address`, fields: addressFields, - onFormSuccess: (record: any) => table.updateRecord(record) + table: table }); const deleteAddress = useDeleteApiFormModal({ url: ApiEndpoints.address_list, pk: selectedAddress, title: t`Delete Address`, - onFormSuccess: table.refreshTable, - preFormWarning: t`Are you sure you want to delete this address?` + preFormWarning: t`Are you sure you want to delete this address?`, + table: table }); const rowActions = useCallback( diff --git a/src/frontend/src/tables/company/ContactTable.tsx b/src/frontend/src/tables/company/ContactTable.tsx index 83e4e6ebf8..763bbaf923 100644 --- a/src/frontend/src/tables/company/ContactTable.tsx +++ b/src/frontend/src/tables/company/ContactTable.tsx @@ -80,14 +80,14 @@ export function ContactTable({ company: companyId }, fields: contactFields, - onFormSuccess: table.refreshTable + table: table }); const deleteContact = useDeleteApiFormModal({ url: ApiEndpoints.contact_list, pk: selectedContact, title: t`Delete Contact`, - onFormSuccess: table.refreshTable + table: table }); const rowActions = useCallback( diff --git a/src/frontend/src/tables/part/PartCategoryTable.tsx b/src/frontend/src/tables/part/PartCategoryTable.tsx index 8f78438b18..90576ee729 100644 --- a/src/frontend/src/tables/part/PartCategoryTable.tsx +++ b/src/frontend/src/tables/part/PartCategoryTable.tsx @@ -1,6 +1,5 @@ import { t } from '@lingui/macro'; import { useCallback, useMemo, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; import { AddItemButton } from '../../components/buttons/AddItemButton'; import { YesNoButton } from '../../components/buttons/YesNoButton'; @@ -8,7 +7,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; import { partCategoryFields } from '../../forms/PartForms'; -import { getDetailUrl } from '../../functions/urls'; import { useCreateApiFormModal, useEditApiFormModal @@ -26,8 +24,6 @@ import { RowEditAction } from '../RowActions'; * PartCategoryTable - Displays a table of part categories */ export function PartCategoryTable({ parentId }: { parentId?: any }) { - const navigate = useNavigate(); - const table = useTable('partcategory'); const user = useUserState(); @@ -79,13 +75,9 @@ export function PartCategoryTable({ parentId }: { parentId?: any }) { initialData: { parent: parentId }, - onFormSuccess(data: any) { - if (data.pk) { - navigate(getDetailUrl(ModelType.partcategory, data.pk)); - } else { - table.refreshTable(); - } - } + follow: true, + modelType: ModelType.partcategory, + table: table }); const [selectedCategory, setSelectedCategory] = useState(-1); diff --git a/src/frontend/src/tables/part/PartCategoryTemplateTable.tsx b/src/frontend/src/tables/part/PartCategoryTemplateTable.tsx index 861747cb9b..e44b756df1 100644 --- a/src/frontend/src/tables/part/PartCategoryTemplateTable.tsx +++ b/src/frontend/src/tables/part/PartCategoryTemplateTable.tsx @@ -37,7 +37,7 @@ export default function PartCategoryTemplateTable({}: {}) { url: ApiEndpoints.category_parameter_list, title: t`Add Category Parameter`, fields: useMemo(() => ({ ...formFields }), [formFields]), - onFormSuccess: table.refreshTable + table: table }); const editTemplate = useEditApiFormModal({ @@ -45,14 +45,14 @@ export default function PartCategoryTemplateTable({}: {}) { pk: selectedTemplate, title: t`Edit Category Parameter`, fields: useMemo(() => ({ ...formFields }), [formFields]), - onFormSuccess: (record: any) => table.updateRecord(record) + table: table }); const deleteTemplate = useDeleteApiFormModal({ url: ApiEndpoints.category_parameter_list, pk: selectedTemplate, title: t`Delete Category Parameter`, - onFormSuccess: table.refreshTable + table: table }); const tableFilters: TableFilter[] = useMemo(() => { diff --git a/src/frontend/src/tables/part/PartParameterTable.tsx b/src/frontend/src/tables/part/PartParameterTable.tsx index f6550d642d..c01f37118a 100644 --- a/src/frontend/src/tables/part/PartParameterTable.tsx +++ b/src/frontend/src/tables/part/PartParameterTable.tsx @@ -115,7 +115,7 @@ export function PartParameterTable({ partId }: { partId: any }) { initialData: { part: partId }, - onFormSuccess: table.refreshTable + table: table }); const [selectedParameter, setSelectedParameter] = useState< @@ -127,14 +127,14 @@ export function PartParameterTable({ partId }: { partId: any }) { pk: selectedParameter, title: t`Edit Part Parameter`, fields: useMemo(() => ({ ...partParameterFields }), [partParameterFields]), - onFormSuccess: table.refreshTable + table: table }); const deleteParameter = useDeleteApiFormModal({ url: ApiEndpoints.part_parameter_list, pk: selectedParameter, title: t`Delete Part Parameter`, - onFormSuccess: table.refreshTable + table: table }); // Callback for row actions @@ -171,6 +171,7 @@ export function PartParameterTable({ partId }: { partId: any }) { const tableActions = useMemo(() => { return [ ), - onFormSuccess: table.refreshTable + table: table }); const rowActions = useCallback( diff --git a/src/frontend/src/tables/part/RelatedPartTable.tsx b/src/frontend/src/tables/part/RelatedPartTable.tsx index fc082a4a0b..8b4c23004b 100644 --- a/src/frontend/src/tables/part/RelatedPartTable.tsx +++ b/src/frontend/src/tables/part/RelatedPartTable.tsx @@ -86,7 +86,7 @@ export function RelatedPartTable({ partId }: { partId: number }): ReactNode { initialData: { part_1: partId }, - onFormSuccess: table.refreshTable + table: table }); const [selectedRelatedPart, setSelectedRelatedPart] = useState< @@ -97,7 +97,7 @@ export function RelatedPartTable({ partId }: { partId: number }): ReactNode { url: ApiEndpoints.related_part_list, pk: selectedRelatedPart, title: t`Delete Related Part`, - onFormSuccess: table.refreshTable + table: table }); const tableActions: ReactNode[] = useMemo(() => { diff --git a/src/frontend/src/tables/plugin/PluginListTable.tsx b/src/frontend/src/tables/plugin/PluginListTable.tsx index 690797decc..b4123b5473 100644 --- a/src/frontend/src/tables/plugin/PluginListTable.tsx +++ b/src/frontend/src/tables/plugin/PluginListTable.tsx @@ -10,12 +10,10 @@ import { Title, Tooltip } from '@mantine/core'; -import { modals } from '@mantine/modals'; -import { notifications, showNotification } from '@mantine/notifications'; +import { showNotification } from '@mantine/notifications'; import { IconCircleCheck, IconCircleX, - IconDots, IconHelpCircle, IconInfoCircle, IconPlaylistAdd, @@ -26,16 +24,11 @@ import { useNavigate } from 'react-router-dom'; import { api } from '../../App'; import { ActionButton } from '../../components/buttons/ActionButton'; -import { - ActionDropdown, - EditItemAction -} from '../../components/items/ActionDropdown'; +import { YesNoButton } from '../../components/buttons/YesNoButton'; import { InfoItem } from '../../components/items/InfoItem'; -import { StylishText } from '../../components/items/StylishText'; import { DetailDrawer } from '../../components/nav/DetailDrawer'; import { PluginSettingList } from '../../components/settings/SettingList'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; -import { openEditApiForm } from '../../functions/forms'; import { useCreateApiFormModal, useDeleteApiFormModal, @@ -80,16 +73,9 @@ export interface PluginI { >; } -export function PluginDrawer({ - pluginKey, - refreshTable -}: { - pluginKey: string; - refreshTable: () => void; -}) { +export function PluginDrawer({ pluginKey }: { pluginKey: Readonly }) { const { instance: plugin, - refreshInstance, instanceQuery: { isFetching, error } } = useInstance({ endpoint: ApiEndpoints.plugin_list, @@ -98,11 +84,6 @@ export function PluginDrawer({ throwError: true }); - const refetch = useCallback(() => { - refreshTable(); - refreshInstance(); - }, [refreshTable, refreshInstance]); - if (!pluginKey || isFetching) { return ; } @@ -121,44 +102,18 @@ export function PluginDrawer({ return ( - - + + + - - {plugin && } - - {plugin?.meta?.human_name ?? plugin?.name ?? '-'} - + + {plugin && } + + {plugin?.meta?.human_name ?? plugin?.name ?? '-'} + + - - } - actions={[ - EditItemAction({ - tooltip: t`Edit plugin`, - onClick: () => { - openEditApiForm({ - title: t`Edit plugin`, - url: ApiEndpoints.plugin_list, - pathParams: { key: pluginKey }, - fields: { - active: {} - }, - onClose: refetch - }); - } - }), - { - name: t`Reload`, - tooltip: t`Reload`, - icon: , - onClick: refreshInstance - } - ]} - /> - - + @@ -166,64 +121,74 @@ export function PluginDrawer({ <Trans>Plugin information</Trans> - - - - - - - - + {plugin.active ? ( + + + + + + + + + ) : ( + {t`Plugin is not active`} + )} - - - - <Trans>Package information</Trans> - - - {plugin?.is_package && ( + {plugin.active && ( + + + + <Trans>Package information</Trans> + + + {plugin?.is_package && ( + + )} - )} - - - + + + - - + + )} {plugin && plugin?.active && ( @@ -300,6 +265,12 @@ export default function PluginListTable() { ); } }, + { + accessor: 'active', + sortable: true, + title: t`Active`, + render: (record: any) => + }, { accessor: 'meta.description', title: t`Description`, @@ -312,6 +283,7 @@ export default function PluginListTable() { return ( {t`Description not available`} ); } @@ -333,87 +305,49 @@ export default function PluginListTable() { [] ); - const activatePlugin = useCallback( - (plugin_key: string, plugin_name: string, active: boolean) => { - modals.openConfirmModal({ - title: ( - - {active ? t`Activate Plugin` : t`Deactivate Plugin`} - - ), - children: ( - } - title={ - active - ? t`Confirm plugin activation` - : t`Confirm plugin deactivation` - } - > - - - {active - ? t`The following plugin will be activated` - : t`The following plugin will be deactivated`} - : - - - {plugin_name} - - - - ), - labels: { - cancel: t`Cancel`, - confirm: t`Confirm` - }, - onConfirm: () => { - let url = apiUrl(ApiEndpoints.plugin_activate, null, { - key: plugin_key - }); + const [selectedPlugin, setSelectedPlugin] = useState(''); + const [activate, setActivate] = useState(false); - const id = 'plugin-activate'; + const activateModalContent = useMemo(() => { + return ( + + } + title={ + activate + ? t`Confirm plugin activation` + : t`Confirm plugin deactivation` + } + > + + {activate + ? t`The selected plugin will be activated` + : t`The selected plugin will be deactivated`} + + + + ); + }, [activate]); - // Show a progress notification - notifications.show({ - id: id, - message: active ? t`Activating plugin` : t`Deactivating plugin`, - loading: true - }); - - api - .patch( - url, - { active: active }, - { - timeout: 30 * 1000 - } - ) - .then(() => { - table.refreshTable(); - notifications.hide(id); - notifications.show({ - title: t`Plugin updated`, - message: active - ? t`The plugin was activated` - : t`The plugin was deactivated`, - color: 'green' - }); - }) - .catch((_err) => { - notifications.hide(id); - notifications.show({ - title: t`Error`, - message: t`Error updating plugin`, - color: 'red' - }); - }); - } - }); + const activatePluginModal = useEditApiFormModal({ + title: t`Activate Plugin`, + url: ApiEndpoints.plugin_activate, + pathParams: { key: selectedPlugin }, + preFormContent: activateModalContent, + fetchInitialData: false, + method: 'POST', + successMessage: activate + ? `The plugin was activated` + : `The plugin was deactivated`, + fields: { + active: { + value: activate, + hidden: true + } }, - [] - ); + table: table + }); // Determine available actions for a given plugin const rowActions = useCallback( @@ -429,7 +363,9 @@ export default function PluginListTable() { color: 'red', icon: , onClick: () => { - activatePlugin(record.key, record.name, false); + setSelectedPlugin(record.key); + setActivate(false); + activatePluginModal.open(); } }); } else { @@ -438,7 +374,9 @@ export default function PluginListTable() { color: 'green', icon: , onClick: () => { - activatePlugin(record.key, record.name, true); + setSelectedPlugin(record.key); + setActivate(true); + activatePluginModal.open(); } }); } @@ -511,21 +449,10 @@ export default function PluginListTable() { }, closeOnClickOutside: false, submitText: t`Install`, - successMessage: undefined, - onFormSuccess: (data) => { - notifications.show({ - title: t`Plugin installed successfully`, - message: data.result, - autoClose: 30000, - color: 'green' - }); - - table.refreshTable(); - } + successMessage: t`Plugin installed successfully`, + table: table }); - const [selectedPlugin, setSelectedPlugin] = useState(''); - const uninstallPluginModal = useEditApiFormModal({ title: t`Uninstall Plugin`, url: ApiEndpoints.plugin_uninstall, @@ -547,24 +474,16 @@ export default function PluginListTable() { ), - onFormSuccess: (data) => { - notifications.show({ - title: t`Plugin uninstalled successfully`, - message: data.result, - autoClose: 30000, - color: 'green' - }); - - table.refreshTable(); - } + successMessage: t`Plugin uninstalled successfully`, + table: table }); const deletePluginModal = useDeleteApiFormModal({ url: ApiEndpoints.plugin_list, pathParams: { key: selectedPlugin }, title: t`Delete Plugin`, - onFormSuccess: table.refreshTable, - preFormWarning: t`Deleting this plugin configuration will remove all associated settings and data. Are you sure you want to delete this plugin?` + preFormWarning: t`Deleting this plugin configuration will remove all associated settings and data. Are you sure you want to delete this plugin?`, + table: table }); const reloadPlugins = useCallback(() => { @@ -617,6 +536,7 @@ export default function PluginListTable() { return ( <> + {activatePluginModal.modal} {installPluginModal.modal} {uninstallPluginModal.modal} {deletePluginModal.modal} @@ -625,12 +545,7 @@ export default function PluginListTable() { size={'50%'} renderContent={(pluginKey) => { if (!pluginKey) return; - return ( - - ); + return ; }} /> (undefined); + + const createParameter = useCreateApiFormModal({ + url: ApiEndpoints.manufacturer_part_parameter_list, + title: t`Add Parameter`, + fields: fields, + table: table, + initialData: { + manufacturer_part: params.manufacturer_part + } + }); + + const editParameter = useEditApiFormModal({ + url: ApiEndpoints.manufacturer_part_parameter_list, + pk: selectedParameter, + title: t`Edit Parameter`, + fields: fields, + table: table + }); + + const deleteParameter = useDeleteApiFormModal({ + url: ApiEndpoints.manufacturer_part_parameter_list, + pk: selectedParameter, + title: t`Delete Parameter`, + table: table + }); + const rowActions = useCallback( (record: any) => { return [ RowEditAction({ hidden: !user.hasChangeRole(UserRoles.purchase_order), onClick: () => { - openEditApiForm({ - url: ApiEndpoints.manufacturer_part_parameter_list, - pk: record.pk, - title: t`Edit Parameter`, - fields: fields, - onFormSuccess: table.refreshTable, - successMessage: t`Parameter updated` - }); + setSelectedParameter(record.pk); + editParameter.open(); } }), RowDeleteAction({ hidden: !user.hasDeleteRole(UserRoles.purchase_order), onClick: () => { - record.pk && - openDeleteApiForm({ - url: ApiEndpoints.manufacturer_part_parameter_list, - pk: record.pk, - title: t`Delete Parameter`, - onFormSuccess: table.refreshTable, - successMessage: t`Parameter deleted`, - preFormWarning: t`Are you sure you want to delete this parameter?` - }); + setSelectedParameter(record.pk); + deleteParameter.open(); } }) ]; @@ -80,17 +101,36 @@ export default function ManufacturerPartParameterTable({ [user] ); + const tableActions = useMemo(() => { + return [ + { + createParameter.open(); + }} + hidden={!user.hasAddRole(UserRoles.purchase_order)} + /> + ]; + }, [user]); + return ( - + <> + {createParameter.modal} + {editParameter.modal} + {deleteParameter.modal} + + ); } diff --git a/src/frontend/src/tables/purchasing/ManufacturerPartTable.tsx b/src/frontend/src/tables/purchasing/ManufacturerPartTable.tsx index fb91b1c74d..e862f10359 100644 --- a/src/frontend/src/tables/purchasing/ManufacturerPartTable.tsx +++ b/src/frontend/src/tables/purchasing/ManufacturerPartTable.tsx @@ -1,5 +1,5 @@ import { t } from '@lingui/macro'; -import { ReactNode, useCallback, useMemo } from 'react'; +import { ReactNode, useCallback, useMemo, useState } from 'react'; import { AddItemButton } from '../../components/buttons/AddItemButton'; import { Thumbnail } from '../../components/images/Thumbnail'; @@ -7,8 +7,11 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; import { useManufacturerPartFields } from '../../forms/CompanyForms'; -import { openDeleteApiForm, openEditApiForm } from '../../functions/forms'; -import { useCreateApiFormModal } from '../../hooks/UseForm'; +import { + useCreateApiFormModal, + useDeleteApiFormModal, + useEditApiFormModal +} from '../../hooks/UseForm'; import { useTable } from '../../hooks/UseTable'; import { apiUrl } from '../../states/ApiState'; import { useUserState } from '../../states/UserState'; @@ -58,16 +61,37 @@ export function ManufacturerPartTable({ params }: { params: any }): ReactNode { ]; }, [params]); + const manufacturerPartFields = useManufacturerPartFields(); + + const [selectedPart, setSelectedPart] = useState( + undefined + ); + const createManufacturerPart = useCreateApiFormModal({ url: ApiEndpoints.manufacturer_part_list, title: t`Add Manufacturer Part`, - fields: useManufacturerPartFields(), - onFormSuccess: table.refreshTable, + fields: manufacturerPartFields, + table: table, initialData: { manufacturer: params?.manufacturer } }); + const editManufacturerPart = useEditApiFormModal({ + url: ApiEndpoints.manufacturer_part_list, + pk: selectedPart, + title: t`Edit Manufacturer Part`, + fields: manufacturerPartFields, + table: table + }); + + const deleteManufacturerPart = useDeleteApiFormModal({ + url: ApiEndpoints.manufacturer_part_list, + pk: selectedPart, + title: t`Delete Manufacturer Part`, + table: table + }); + const tableActions = useMemo(() => { let can_add = user.hasAddRole(UserRoles.purchase_order) && @@ -82,37 +106,21 @@ export function ManufacturerPartTable({ params }: { params: any }): ReactNode { ]; }, [user]); - const editManufacturerPartFields = useManufacturerPartFields(); - const rowActions = useCallback( (record: any) => { return [ RowEditAction({ hidden: !user.hasChangeRole(UserRoles.purchase_order), onClick: () => { - record.pk && - openEditApiForm({ - url: ApiEndpoints.manufacturer_part_list, - pk: record.pk, - title: t`Edit Manufacturer Part`, - fields: editManufacturerPartFields, - onFormSuccess: table.refreshTable, - successMessage: t`Manufacturer part updated` - }); + setSelectedPart(record.pk); + editManufacturerPart.open(); } }), RowDeleteAction({ hidden: !user.hasDeleteRole(UserRoles.purchase_order), onClick: () => { - record.pk && - openDeleteApiForm({ - url: ApiEndpoints.manufacturer_part_list, - pk: record.pk, - title: t`Delete Manufacturer Part`, - successMessage: t`Manufacturer part deleted`, - onFormSuccess: table.refreshTable, - preFormWarning: t`Are you sure you want to remove this manufacturer part?` - }); + setSelectedPart(record.pk); + deleteManufacturerPart.open(); } }) ]; @@ -123,6 +131,8 @@ export function ManufacturerPartTable({ params }: { params: any }): ReactNode { return ( <> {createManufacturerPart.modal} + {editManufacturerPart.modal} + {deleteManufacturerPart.modal} (0); @@ -214,14 +214,14 @@ export function PurchaseOrderLineItemTable({ pk: selectedLine, title: t`Edit Line Item`, fields: editPurchaseOrderFields, - onFormSuccess: table.refreshTable + table: table }); const deleteLine = useDeleteApiFormModal({ url: ApiEndpoints.purchase_order_line_list, pk: selectedLine, title: t`Delete Line Item`, - onFormSuccess: table.refreshTable + table: table }); const rowActions = useCallback( diff --git a/src/frontend/src/tables/purchasing/SupplierPartTable.tsx b/src/frontend/src/tables/purchasing/SupplierPartTable.tsx index 3a6af2b841..0b3f21ba3e 100644 --- a/src/frontend/src/tables/purchasing/SupplierPartTable.tsx +++ b/src/frontend/src/tables/purchasing/SupplierPartTable.tsx @@ -166,7 +166,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode { part: params?.part, supplier: params?.supplier }, - onFormSuccess: table.refreshTable, + table: table, successMessage: t`Supplier part created` }); @@ -209,14 +209,14 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode { pk: selectedSupplierPart, title: t`Edit Supplier Part`, fields: editSupplierPartFields, - onFormSuccess: () => table.refreshTable() + table: table }); const deleteSupplierPart = useDeleteApiFormModal({ url: ApiEndpoints.supplier_part_list, pk: selectedSupplierPart, title: t`Delete Supplier Part`, - onFormSuccess: () => table.refreshTable() + table: table }); // Row action callback diff --git a/src/frontend/src/tables/purchasing/SupplierPriceBreakTable.tsx b/src/frontend/src/tables/purchasing/SupplierPriceBreakTable.tsx index d2d0950ce7..56ee44e8f0 100644 --- a/src/frontend/src/tables/purchasing/SupplierPriceBreakTable.tsx +++ b/src/frontend/src/tables/purchasing/SupplierPriceBreakTable.tsx @@ -140,9 +140,7 @@ export default function SupplierPriceBreakTable({ initialData: { part: supplierPartId }, - onFormSuccess: (data: any) => { - table.refreshTable(); - } + table: table }); const editPriceBreak = useEditApiFormModal({ @@ -150,18 +148,14 @@ export default function SupplierPriceBreakTable({ pk: selectedPriceBreak, title: t`Edit Price Break`, fields: supplierPriceBreakFields, - onFormSuccess: (data: any) => { - table.refreshTable(); - } + table: table }); const deletePriceBreak = useDeleteApiFormModal({ url: apiUrl(ApiEndpoints.supplier_part_pricing_list), pk: selectedPriceBreak, title: t`Delete Price Break`, - onFormSuccess: () => { - table.refreshTable(); - } + table: table }); const tableActions = useMemo(() => { diff --git a/src/frontend/src/tables/settings/CustomUnitsTable.tsx b/src/frontend/src/tables/settings/CustomUnitsTable.tsx index 75f0195af6..3d0f2341c3 100644 --- a/src/frontend/src/tables/settings/CustomUnitsTable.tsx +++ b/src/frontend/src/tables/settings/CustomUnitsTable.tsx @@ -49,7 +49,7 @@ export default function CustomUnitsTable() { url: ApiEndpoints.custom_unit_list, title: t`Add Custom Unit`, fields: customUnitsFields(), - onFormSuccess: table.refreshTable + table: table }); const [selectedUnit, setSelectedUnit] = useState(-1); @@ -66,7 +66,7 @@ export default function CustomUnitsTable() { url: ApiEndpoints.custom_unit_list, pk: selectedUnit, title: t`Delete Custom Unit`, - onFormSuccess: table.refreshTable + table: table }); const rowActions = useCallback( diff --git a/src/frontend/src/tables/settings/ErrorTable.tsx b/src/frontend/src/tables/settings/ErrorTable.tsx index 4fec4e3c34..e313d0b542 100644 --- a/src/frontend/src/tables/settings/ErrorTable.tsx +++ b/src/frontend/src/tables/settings/ErrorTable.tsx @@ -5,7 +5,7 @@ import { useCallback, useMemo, useState } from 'react'; import { StylishText } from '../../components/items/StylishText'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; -import { openDeleteApiForm } from '../../functions/forms'; +import { useDeleteApiFormModal } from '../../hooks/UseForm'; import { useTable } from '../../hooks/UseTable'; import { apiUrl } from '../../states/ApiState'; import { TableColumn } from '../Column'; @@ -41,18 +41,27 @@ export default function ErrorReportTable() { ]; }, []); + const [selectedError, setSelectedError] = useState( + undefined + ); + + const deleteErrorModal = useDeleteApiFormModal({ + url: ApiEndpoints.error_report_list, + pk: selectedError, + title: t`Delete Error Report`, + preFormContent: ( + {t`Are you sure you want to delete this error report?`} + ), + successMessage: t`Error report deleted`, + table: table + }); + const rowActions = useCallback((record: any): RowAction[] => { return [ RowDeleteAction({ onClick: () => { - openDeleteApiForm({ - url: ApiEndpoints.error_report_list, - pk: record.pk, - title: t`Delete error report`, - onFormSuccess: table.refreshTable, - successMessage: t`Error report deleted`, - preFormWarning: t`Are you sure you want to delete this error report?` - }); + setSelectedError(record.pk); + deleteErrorModal.open(); } }) ]; @@ -60,6 +69,7 @@ export default function ErrorReportTable() { return ( <> + {deleteErrorModal.modal} { diff --git a/src/frontend/src/tables/settings/ProjectCodeTable.tsx b/src/frontend/src/tables/settings/ProjectCodeTable.tsx index b365cc372d..ee21364d34 100644 --- a/src/frontend/src/tables/settings/ProjectCodeTable.tsx +++ b/src/frontend/src/tables/settings/ProjectCodeTable.tsx @@ -41,7 +41,7 @@ export default function ProjectCodeTable() { url: ApiEndpoints.project_code_list, title: t`Add Project Code`, fields: projectCodeFields(), - onFormSuccess: table.refreshTable + table: table }); const [selectedProjectCode, setSelectedProjectCode] = useState< @@ -53,14 +53,14 @@ export default function ProjectCodeTable() { pk: selectedProjectCode, title: t`Edit Project Code`, fields: projectCodeFields(), - onFormSuccess: (record: any) => table.updateRecord(record) + table: table }); const deleteProjectCode = useDeleteApiFormModal({ url: ApiEndpoints.project_code_list, pk: selectedProjectCode, title: t`Delete Project Code`, - onFormSuccess: table.refreshTable + table: table }); const rowActions = useCallback( diff --git a/src/frontend/src/tables/settings/TemplateTable.tsx b/src/frontend/src/tables/settings/TemplateTable.tsx index e1928430ad..724fa79a66 100644 --- a/src/frontend/src/tables/settings/TemplateTable.tsx +++ b/src/frontend/src/tables/settings/TemplateTable.tsx @@ -234,7 +234,7 @@ export function TemplateTable({ pathParams: { variant }, pk: selectedTemplate, title: t`Delete` + ' ' + templateTypeTranslation, - onFormSuccess: table.refreshTable + table: table }); const newTemplate = useCreateApiFormModal({ diff --git a/src/frontend/src/tables/settings/UserTable.tsx b/src/frontend/src/tables/settings/UserTable.tsx index e2793d1c8d..a7108e64f5 100644 --- a/src/frontend/src/tables/settings/UserTable.tsx +++ b/src/frontend/src/tables/settings/UserTable.tsx @@ -230,7 +230,7 @@ export function UserTable() { pk: selectedUser, title: t`Delete user`, successMessage: t`User deleted`, - onFormSuccess: table.refreshTable, + table: table, preFormWarning: t`Are you sure you want to delete this user?` }); @@ -244,7 +244,7 @@ export function UserTable() { first_name: {}, last_name: {} }, - onFormSuccess: table.refreshTable, + table: table, successMessage: t`Added user` }); diff --git a/src/frontend/src/tables/stock/StockItemTestResultTable.tsx b/src/frontend/src/tables/stock/StockItemTestResultTable.tsx index ba38d376ce..2439ba870b 100644 --- a/src/frontend/src/tables/stock/StockItemTestResultTable.tsx +++ b/src/frontend/src/tables/stock/StockItemTestResultTable.tsx @@ -268,7 +268,7 @@ export default function StockItemTestResultTable({ result: true }, title: t`Add Test Result`, - onFormSuccess: () => table.refreshTable(), + table: table, successMessage: t`Test result added` }); @@ -279,7 +279,7 @@ export default function StockItemTestResultTable({ pk: selectedTest, fields: resultFields, title: t`Edit Test Result`, - onFormSuccess: () => table.refreshTable(), + table: table, successMessage: t`Test result updated` }); @@ -287,7 +287,7 @@ export default function StockItemTestResultTable({ url: ApiEndpoints.stock_test_result_list, pk: selectedTest, title: t`Delete Test Result`, - onFormSuccess: () => table.refreshTable(), + table: table, successMessage: t`Test result deleted` }); diff --git a/src/frontend/src/tables/stock/StockLocationTable.tsx b/src/frontend/src/tables/stock/StockLocationTable.tsx index 1b2ef2b328..f8c66a03b7 100644 --- a/src/frontend/src/tables/stock/StockLocationTable.tsx +++ b/src/frontend/src/tables/stock/StockLocationTable.tsx @@ -1,13 +1,11 @@ import { t } from '@lingui/macro'; import { useCallback, useMemo, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; import { AddItemButton } from '../../components/buttons/AddItemButton'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; import { stockLocationFields } from '../../forms/StockForms'; -import { getDetailUrl } from '../../functions/urls'; import { useCreateApiFormModal, useEditApiFormModal @@ -28,8 +26,6 @@ export function StockLocationTable({ parentId }: { parentId?: any }) { const table = useTable('stocklocation'); const user = useUserState(); - const navigate = useNavigate(); - const tableFilters: TableFilter[] = useMemo(() => { return [ { @@ -91,13 +87,9 @@ export function StockLocationTable({ parentId }: { parentId?: any }) { initialData: { parent: parentId }, - onFormSuccess(data: any) { - if (data.pk) { - navigate(getDetailUrl(ModelType.stocklocation, data.pk)); - } else { - table.refreshTable(); - } - } + follow: true, + modelType: ModelType.stocklocation, + table: table }); const [selectedLocation, setSelectedLocation] = useState(-1);