mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
[PUI] Refactoring forms (#7239)
* Refactor "plugin activate" dialog - Use hooked modal * Remove actions from drawer - Used dynamic modal code which is super buggy * Update plugin drawer / table * Refactor settings management: - Use proper hooked form - Reduce code duplication - Run single callback for each <SettingList> * Add error boundary * Update ErrorTable - Use useDeleteApiFormModal * Refactor ManufacturerPartParameter table - Use hooked modals * Refactor existing tables - Pass table state to forms * Ensure table is reloaded * Refactor ManufacturerPartTable * Code cleanup * More cleanup
This commit is contained in:
parent
548ecf58a2
commit
b7b320cf61
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 ? (
|
||||
<Group gap="xs" justify="right">
|
||||
<Space />
|
||||
<Button variant="subtle" onClick={onEditButton}>
|
||||
<Button variant="subtle" onClick={() => onEdit(setting)}>
|
||||
{valueText}
|
||||
</Button>
|
||||
</Group>
|
||||
) : (
|
||||
<Button variant="subtle" onClick={onEditButton}>
|
||||
<Button variant="subtle" onClick={() => onEdit(setting)}>
|
||||
<IconEdit />
|
||||
</Button>
|
||||
);
|
||||
@ -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({
|
||||
</Text>
|
||||
<Text size="xs">{setting.description}</Text>
|
||||
</Stack>
|
||||
<SettingValue
|
||||
settingsState={settingsState}
|
||||
setting={setting}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Boundary label={`setting-value-${setting.key}`}>
|
||||
<SettingValue setting={setting} onEdit={onEdit} onToggle={onToggle} />
|
||||
</Boundary>
|
||||
</Group>
|
||||
</Paper>
|
||||
);
|
||||
|
@ -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<Setting | undefined>(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}
|
||||
<Stack gap="xs">
|
||||
{(keys || allKeys).map((key, i) => {
|
||||
const setting = settingsState?.settings?.find(
|
||||
@ -45,10 +131,10 @@ export function SettingList({
|
||||
<React.Fragment key={key}>
|
||||
{setting ? (
|
||||
<SettingItem
|
||||
settingsState={settingsState}
|
||||
setting={setting}
|
||||
shaded={i % 2 === 0}
|
||||
onChange={onChange}
|
||||
onEdit={onValueEdit}
|
||||
onToggle={onValueToggle}
|
||||
/>
|
||||
) : (
|
||||
<Text size="sm" style={{ fontStyle: 'italic' }} color="red">
|
||||
|
@ -82,6 +82,9 @@ export function useManufacturerPartFields() {
|
||||
export function useManufacturerPartParameterFields() {
|
||||
return useMemo(() => {
|
||||
const fields: ApiFormFieldSet = {
|
||||
manufacturer_part: {
|
||||
disabled: true
|
||||
},
|
||||
name: {},
|
||||
value: {},
|
||||
units: {}
|
||||
|
@ -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(() => {
|
||||
|
@ -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(
|
||||
|
@ -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<any[]>([]);
|
||||
|
@ -124,7 +124,7 @@ export function AddressTable({
|
||||
company: companyId
|
||||
},
|
||||
successMessage: t`Address created`,
|
||||
onFormSuccess: table.refreshTable
|
||||
table: table
|
||||
});
|
||||
|
||||
const [selectedAddress, setSelectedAddress] = useState<number>(-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(
|
||||
|
@ -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(
|
||||
|
@ -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<number>(-1);
|
||||
|
@ -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(() => {
|
||||
|
@ -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 [
|
||||
<AddItemButton
|
||||
key="add-parameter"
|
||||
hidden={!user.hasAddRole(UserRoles.part)}
|
||||
tooltip={t`Add parameter`}
|
||||
onClick={() => newParameter.open()}
|
||||
|
@ -83,11 +83,11 @@ export default function PartParameterTemplateTable() {
|
||||
const newTemplate = useCreateApiFormModal({
|
||||
url: ApiEndpoints.part_parameter_template_list,
|
||||
title: t`Add Parameter Template`,
|
||||
table: table,
|
||||
fields: useMemo(
|
||||
() => ({ ...partParameterTemplateFields }),
|
||||
[partParameterTemplateFields]
|
||||
),
|
||||
onFormSuccess: table.refreshTable
|
||||
)
|
||||
});
|
||||
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<number | undefined>(
|
||||
@ -98,18 +98,18 @@ export default function PartParameterTemplateTable() {
|
||||
url: ApiEndpoints.part_parameter_template_list,
|
||||
pk: selectedTemplate,
|
||||
title: t`Edit Parameter Template`,
|
||||
table: table,
|
||||
fields: useMemo(
|
||||
() => ({ ...partParameterTemplateFields }),
|
||||
[partParameterTemplateFields]
|
||||
),
|
||||
onFormSuccess: (record: any) => table.updateRecord(record)
|
||||
)
|
||||
});
|
||||
|
||||
const deleteTemplate = useDeleteApiFormModal({
|
||||
url: ApiEndpoints.part_parameter_template_list,
|
||||
pk: selectedTemplate,
|
||||
title: t`Delete Parameter Template`,
|
||||
onFormSuccess: table.refreshTable
|
||||
table: table
|
||||
});
|
||||
|
||||
// Callback for row actions
|
||||
|
@ -131,7 +131,7 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) {
|
||||
initialData: {
|
||||
part: partId
|
||||
},
|
||||
onFormSuccess: table.refreshTable
|
||||
table: table
|
||||
});
|
||||
|
||||
const [selectedTest, setSelectedTest] = useState<number>(-1);
|
||||
@ -140,11 +140,11 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) {
|
||||
url: ApiEndpoints.part_test_template_list,
|
||||
pk: selectedTest,
|
||||
title: t`Edit Test Template`,
|
||||
table: table,
|
||||
fields: useMemo(
|
||||
() => ({ ...partTestTemplateFields }),
|
||||
[partTestTemplateFields]
|
||||
),
|
||||
onFormSuccess: (record: any) => table.updateRecord(record)
|
||||
)
|
||||
});
|
||||
|
||||
const deleteTestTemplate = useDeleteApiFormModal({
|
||||
@ -160,7 +160,7 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) {
|
||||
</Text>
|
||||
</Alert>
|
||||
),
|
||||
onFormSuccess: table.refreshTable
|
||||
table: table
|
||||
});
|
||||
|
||||
const rowActions = useCallback(
|
||||
|
@ -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(() => {
|
||||
|
@ -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<string> }) {
|
||||
const {
|
||||
instance: plugin,
|
||||
refreshInstance,
|
||||
instanceQuery: { isFetching, error }
|
||||
} = useInstance<PluginI>({
|
||||
endpoint: ApiEndpoints.plugin_list,
|
||||
@ -98,11 +84,6 @@ export function PluginDrawer({
|
||||
throwError: true
|
||||
});
|
||||
|
||||
const refetch = useCallback(() => {
|
||||
refreshTable();
|
||||
refreshInstance();
|
||||
}, [refreshTable, refreshInstance]);
|
||||
|
||||
if (!pluginKey || isFetching) {
|
||||
return <LoadingOverlay visible={true} />;
|
||||
}
|
||||
@ -121,44 +102,18 @@ export function PluginDrawer({
|
||||
|
||||
return (
|
||||
<Stack gap={'xs'}>
|
||||
<Group justify="space-between">
|
||||
<Box></Box>
|
||||
<Card withBorder>
|
||||
<Group justify="left">
|
||||
<Box></Box>
|
||||
|
||||
<Group gap={'xs'}>
|
||||
{plugin && <PluginIcon plugin={plugin} />}
|
||||
<Title order={4}>
|
||||
{plugin?.meta?.human_name ?? plugin?.name ?? '-'}
|
||||
</Title>
|
||||
<Group gap={'xs'}>
|
||||
{plugin && <PluginIcon plugin={plugin} />}
|
||||
<Title order={4}>
|
||||
{plugin?.meta?.human_name ?? plugin?.name ?? '-'}
|
||||
</Title>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<ActionDropdown
|
||||
tooltip={t`Plugin Actions`}
|
||||
icon={<IconDots />}
|
||||
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: <IconRefresh />,
|
||||
onClick: refreshInstance
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
</Card>
|
||||
<LoadingOverlay visible={isFetching} overlayProps={{ opacity: 0 }} />
|
||||
|
||||
<Card withBorder>
|
||||
@ -166,64 +121,74 @@ export function PluginDrawer({
|
||||
<Title order={4}>
|
||||
<Trans>Plugin information</Trans>
|
||||
</Title>
|
||||
<Stack pos="relative" gap="xs">
|
||||
<InfoItem type="text" name={t`Name`} value={plugin?.name} />
|
||||
<InfoItem
|
||||
type="text"
|
||||
name={t`Description`}
|
||||
value={plugin?.meta.description}
|
||||
/>
|
||||
<InfoItem
|
||||
type="text"
|
||||
name={t`Author`}
|
||||
value={plugin?.meta.author}
|
||||
/>
|
||||
<InfoItem
|
||||
type="text"
|
||||
name={t`Date`}
|
||||
value={plugin?.meta.pub_date}
|
||||
/>
|
||||
<InfoItem
|
||||
type="text"
|
||||
name={t`Version`}
|
||||
value={plugin?.meta.version}
|
||||
/>
|
||||
<InfoItem type="boolean" name={t`Active`} value={plugin?.active} />
|
||||
</Stack>
|
||||
{plugin.active ? (
|
||||
<Stack pos="relative" gap="xs">
|
||||
<InfoItem type="text" name={t`Name`} value={plugin?.name} />
|
||||
<InfoItem
|
||||
type="text"
|
||||
name={t`Description`}
|
||||
value={plugin?.meta.description}
|
||||
/>
|
||||
<InfoItem
|
||||
type="text"
|
||||
name={t`Author`}
|
||||
value={plugin?.meta.author}
|
||||
/>
|
||||
<InfoItem
|
||||
type="text"
|
||||
name={t`Date`}
|
||||
value={plugin?.meta.pub_date}
|
||||
/>
|
||||
<InfoItem
|
||||
type="text"
|
||||
name={t`Version`}
|
||||
value={plugin?.meta.version}
|
||||
/>
|
||||
<InfoItem
|
||||
type="boolean"
|
||||
name={t`Active`}
|
||||
value={plugin?.active}
|
||||
/>
|
||||
</Stack>
|
||||
) : (
|
||||
<Text color="red">{t`Plugin is not active`}</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
<Card withBorder>
|
||||
<Stack gap="md">
|
||||
<Title order={4}>
|
||||
<Trans>Package information</Trans>
|
||||
</Title>
|
||||
<Stack pos="relative" gap="xs">
|
||||
{plugin?.is_package && (
|
||||
{plugin.active && (
|
||||
<Card withBorder>
|
||||
<Stack gap="md">
|
||||
<Title order={4}>
|
||||
<Trans>Package information</Trans>
|
||||
</Title>
|
||||
<Stack pos="relative" gap="xs">
|
||||
{plugin?.is_package && (
|
||||
<InfoItem
|
||||
type="text"
|
||||
name={t`Package Name`}
|
||||
value={plugin?.package_name}
|
||||
/>
|
||||
)}
|
||||
<InfoItem
|
||||
type="text"
|
||||
name={t`Package Name`}
|
||||
value={plugin?.package_name}
|
||||
name={t`Installation Path`}
|
||||
value={plugin?.meta.package_path}
|
||||
/>
|
||||
)}
|
||||
<InfoItem
|
||||
type="text"
|
||||
name={t`Installation Path`}
|
||||
value={plugin?.meta.package_path}
|
||||
/>
|
||||
<InfoItem
|
||||
type="boolean"
|
||||
name={t`Builtin`}
|
||||
value={plugin?.is_builtin}
|
||||
/>
|
||||
<InfoItem
|
||||
type="boolean"
|
||||
name={t`Package`}
|
||||
value={plugin?.is_package}
|
||||
/>
|
||||
<InfoItem
|
||||
type="boolean"
|
||||
name={t`Builtin`}
|
||||
value={plugin?.is_builtin}
|
||||
/>
|
||||
<InfoItem
|
||||
type="boolean"
|
||||
name={t`Package`}
|
||||
value={plugin?.is_package}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{plugin && plugin?.active && (
|
||||
<Card withBorder>
|
||||
@ -300,6 +265,12 @@ export default function PluginListTable() {
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: 'active',
|
||||
sortable: true,
|
||||
title: t`Active`,
|
||||
render: (record: any) => <YesNoButton value={record.active} />
|
||||
},
|
||||
{
|
||||
accessor: 'meta.description',
|
||||
title: t`Description`,
|
||||
@ -312,6 +283,7 @@ export default function PluginListTable() {
|
||||
return (
|
||||
<Text
|
||||
style={{ fontStyle: 'italic' }}
|
||||
size="sm"
|
||||
>{t`Description not available`}</Text>
|
||||
);
|
||||
}
|
||||
@ -333,87 +305,49 @@ export default function PluginListTable() {
|
||||
[]
|
||||
);
|
||||
|
||||
const activatePlugin = useCallback(
|
||||
(plugin_key: string, plugin_name: string, active: boolean) => {
|
||||
modals.openConfirmModal({
|
||||
title: (
|
||||
<StylishText>
|
||||
{active ? t`Activate Plugin` : t`Deactivate Plugin`}
|
||||
</StylishText>
|
||||
),
|
||||
children: (
|
||||
<Alert
|
||||
color="green"
|
||||
icon={<IconCircleCheck />}
|
||||
title={
|
||||
active
|
||||
? t`Confirm plugin activation`
|
||||
: t`Confirm plugin deactivation`
|
||||
}
|
||||
>
|
||||
<Stack gap="xs">
|
||||
<Text>
|
||||
{active
|
||||
? t`The following plugin will be activated`
|
||||
: t`The following plugin will be deactivated`}
|
||||
:
|
||||
</Text>
|
||||
<Text size="lg" style={{ fontStyle: 'italic' }}>
|
||||
{plugin_name}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Alert>
|
||||
),
|
||||
labels: {
|
||||
cancel: t`Cancel`,
|
||||
confirm: t`Confirm`
|
||||
},
|
||||
onConfirm: () => {
|
||||
let url = apiUrl(ApiEndpoints.plugin_activate, null, {
|
||||
key: plugin_key
|
||||
});
|
||||
const [selectedPlugin, setSelectedPlugin] = useState<string>('');
|
||||
const [activate, setActivate] = useState<boolean>(false);
|
||||
|
||||
const id = 'plugin-activate';
|
||||
const activateModalContent = useMemo(() => {
|
||||
return (
|
||||
<Stack gap="xs">
|
||||
<Alert
|
||||
color={activate ? 'green' : 'red'}
|
||||
icon={<IconCircleCheck />}
|
||||
title={
|
||||
activate
|
||||
? t`Confirm plugin activation`
|
||||
: t`Confirm plugin deactivation`
|
||||
}
|
||||
>
|
||||
<Text>
|
||||
{activate
|
||||
? t`The selected plugin will be activated`
|
||||
: t`The selected plugin will be deactivated`}
|
||||
</Text>
|
||||
</Alert>
|
||||
</Stack>
|
||||
);
|
||||
}, [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: <IconCircleX />,
|
||||
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: <IconCircleCheck />,
|
||||
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<string>('');
|
||||
|
||||
const uninstallPluginModal = useEditApiFormModal({
|
||||
title: t`Uninstall Plugin`,
|
||||
url: ApiEndpoints.plugin_uninstall,
|
||||
@ -547,24 +474,16 @@ export default function PluginListTable() {
|
||||
</Stack>
|
||||
</Alert>
|
||||
),
|
||||
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 (
|
||||
<PluginDrawer
|
||||
pluginKey={pluginKey}
|
||||
refreshTable={table.refreshTable}
|
||||
/>
|
||||
);
|
||||
return <PluginDrawer pluginKey={pluginKey} />;
|
||||
}}
|
||||
/>
|
||||
<InvenTreeTable
|
||||
|
@ -1,10 +1,15 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { useManufacturerPartParameterFields } from '../../forms/CompanyForms';
|
||||
import { openDeleteApiForm, openEditApiForm } from '../../functions/forms';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
@ -45,34 +50,50 @@ export default function ManufacturerPartParameterTable({
|
||||
|
||||
const fields = useManufacturerPartParameterFields();
|
||||
|
||||
const [selectedParameter, setSelectedParameter] = useState<
|
||||
number | undefined
|
||||
>(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 [
|
||||
<AddItemButton
|
||||
key="add-parameter"
|
||||
tooltip={t`Add Parameter`}
|
||||
onClick={() => {
|
||||
createParameter.open();
|
||||
}}
|
||||
hidden={!user.hasAddRole(UserRoles.purchase_order)}
|
||||
/>
|
||||
];
|
||||
}, [user]);
|
||||
|
||||
return (
|
||||
<InvenTreeTable
|
||||
url={apiUrl(ApiEndpoints.manufacturer_part_parameter_list)}
|
||||
tableState={table}
|
||||
columns={tableColumns}
|
||||
props={{
|
||||
params: {
|
||||
...params
|
||||
},
|
||||
rowActions: rowActions
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
{createParameter.modal}
|
||||
{editParameter.modal}
|
||||
{deleteParameter.modal}
|
||||
<InvenTreeTable
|
||||
url={apiUrl(ApiEndpoints.manufacturer_part_parameter_list)}
|
||||
tableState={table}
|
||||
columns={tableColumns}
|
||||
props={{
|
||||
params: {
|
||||
...params
|
||||
},
|
||||
rowActions: rowActions,
|
||||
tableActions: tableActions
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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<number | undefined>(
|
||||
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}
|
||||
<InvenTreeTable
|
||||
url={apiUrl(ApiEndpoints.manufacturer_part_list)}
|
||||
tableState={table}
|
||||
|
@ -198,7 +198,7 @@ export function PurchaseOrderLineItemTable({
|
||||
title: t`Add Line Item`,
|
||||
fields: addPurchaseOrderFields,
|
||||
initialData: initialData,
|
||||
onFormSuccess: table.refreshTable
|
||||
table: table
|
||||
});
|
||||
|
||||
const [selectedLine, setSelectedLine] = useState<number>(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(
|
||||
|
@ -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
|
||||
|
@ -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(() => {
|
||||
|
@ -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<number>(-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(
|
||||
|
@ -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<number | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const deleteErrorModal = useDeleteApiFormModal({
|
||||
url: ApiEndpoints.error_report_list,
|
||||
pk: selectedError,
|
||||
title: t`Delete Error Report`,
|
||||
preFormContent: (
|
||||
<Text c="red">{t`Are you sure you want to delete this error report?`}</Text>
|
||||
),
|
||||
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}
|
||||
<Drawer
|
||||
opened={opened}
|
||||
size="xl"
|
||||
|
@ -125,7 +125,7 @@ export function GroupTable() {
|
||||
pk: selectedGroup,
|
||||
title: t`Delete group`,
|
||||
successMessage: t`Group deleted`,
|
||||
onFormSuccess: table.refreshTable,
|
||||
table: table,
|
||||
preFormWarning: t`Are you sure you want to delete this group?`
|
||||
});
|
||||
|
||||
@ -133,7 +133,7 @@ export function GroupTable() {
|
||||
url: ApiEndpoints.group_list,
|
||||
title: t`Add group`,
|
||||
fields: { name: {} },
|
||||
onFormSuccess: table.refreshTable
|
||||
table: table
|
||||
});
|
||||
|
||||
const tableActions = useMemo(() => {
|
||||
|
@ -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(
|
||||
|
@ -234,7 +234,7 @@ export function TemplateTable({
|
||||
pathParams: { variant },
|
||||
pk: selectedTemplate,
|
||||
title: t`Delete` + ' ' + templateTypeTranslation,
|
||||
onFormSuccess: table.refreshTable
|
||||
table: table
|
||||
});
|
||||
|
||||
const newTemplate = useCreateApiFormModal({
|
||||
|
@ -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`
|
||||
});
|
||||
|
||||
|
@ -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`
|
||||
});
|
||||
|
||||
|
@ -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<number>(-1);
|
||||
|
Loading…
Reference in New Issue
Block a user