mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	Admin tweaks (#7248)
* Update admin site - Implement 'autocomplete' for more fields - Improves admin loading time * Add "admin" buttons to the PUI interface * Only allow superuser access
This commit is contained in:
		| @@ -7,6 +7,15 @@ from import_export.admin import ImportExportModelAdmin | |||||||
| import common.models | import common.models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @admin.register(common.models.ProjectCode) | ||||||
|  | class ProjectCodeAdmin(ImportExportModelAdmin): | ||||||
|  |     """Admin settings for ProjectCode.""" | ||||||
|  |  | ||||||
|  |     list_display = ('code', 'description') | ||||||
|  |  | ||||||
|  |     search_fields = ('code', 'description') | ||||||
|  |  | ||||||
|  |  | ||||||
| class SettingsAdmin(ImportExportModelAdmin): | class SettingsAdmin(ImportExportModelAdmin): | ||||||
|     """Admin settings for InvenTreeSetting.""" |     """Admin settings for InvenTreeSetting.""" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -213,6 +213,8 @@ class AddressAdmin(ImportExportModelAdmin): | |||||||
|  |  | ||||||
|     search_fields = ['company', 'country', 'postal_code'] |     search_fields = ['company', 'country', 'postal_code'] | ||||||
|  |  | ||||||
|  |     autocomplete_fields = ['company'] | ||||||
|  |  | ||||||
|  |  | ||||||
| class ContactResource(InvenTreeResource): | class ContactResource(InvenTreeResource): | ||||||
|     """Class for managing Contact data import/export.""" |     """Class for managing Contact data import/export.""" | ||||||
| @@ -237,3 +239,5 @@ class ContactAdmin(ImportExportModelAdmin): | |||||||
|     list_display = ('company', 'name', 'role', 'email', 'phone') |     list_display = ('company', 'name', 'role', 'email', 'phone') | ||||||
|  |  | ||||||
|     search_fields = ['company', 'name', 'email'] |     search_fields = ['company', 'name', 'email'] | ||||||
|  |  | ||||||
|  |     autocomplete_fields = ['company'] | ||||||
|   | |||||||
| @@ -114,7 +114,7 @@ class PurchaseOrderAdmin(ImportExportModelAdmin): | |||||||
|  |  | ||||||
|     inlines = [PurchaseOrderLineItemInlineAdmin] |     inlines = [PurchaseOrderLineItemInlineAdmin] | ||||||
|  |  | ||||||
|     autocomplete_fields = ('supplier',) |     autocomplete_fields = ['supplier', 'project_code', 'contact', 'address'] | ||||||
|  |  | ||||||
|  |  | ||||||
| class SalesOrderResource( | class SalesOrderResource( | ||||||
| @@ -152,7 +152,7 @@ class SalesOrderAdmin(ImportExportModelAdmin): | |||||||
|  |  | ||||||
|     search_fields = ['reference', 'customer__name', 'description'] |     search_fields = ['reference', 'customer__name', 'description'] | ||||||
|  |  | ||||||
|     autocomplete_fields = ('customer',) |     autocomplete_fields = ['customer', 'project_code', 'contact', 'address'] | ||||||
|  |  | ||||||
|  |  | ||||||
| class PurchaseOrderLineItemResource(PriceResourceMixin, InvenTreeResource): | class PurchaseOrderLineItemResource(PriceResourceMixin, InvenTreeResource): | ||||||
| @@ -317,7 +317,7 @@ class ReturnOrderAdmin(ImportExportModelAdmin): | |||||||
|  |  | ||||||
|     search_fields = ['reference', 'customer__name', 'description'] |     search_fields = ['reference', 'customer__name', 'description'] | ||||||
|  |  | ||||||
|     autocomplete_fields = ['customer'] |     autocomplete_fields = ['customer', 'project_code', 'contact', 'address'] | ||||||
|  |  | ||||||
|  |  | ||||||
| class ReturnOrderLineItemResource(PriceResourceMixin, InvenTreeResource): | class ReturnOrderLineItemResource(PriceResourceMixin, InvenTreeResource): | ||||||
|   | |||||||
| @@ -250,6 +250,8 @@ class PartAdmin(ImportExportModelAdmin): | |||||||
|         'category', |         'category', | ||||||
|         'default_location', |         'default_location', | ||||||
|         'default_supplier', |         'default_supplier', | ||||||
|  |         'bom_checked_by', | ||||||
|  |         'creation_user', | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|     inlines = [PartParameterInline] |     inlines = [PartParameterInline] | ||||||
| @@ -260,7 +262,7 @@ class PartPricingAdmin(admin.ModelAdmin): | |||||||
|  |  | ||||||
|     list_display = ('part', 'overall_min', 'overall_max') |     list_display = ('part', 'overall_min', 'overall_max') | ||||||
|  |  | ||||||
|     autcomplete_fields = ['part'] |     autocomplete_fields = ['part'] | ||||||
|  |  | ||||||
|  |  | ||||||
| class PartStocktakeAdmin(admin.ModelAdmin): | class PartStocktakeAdmin(admin.ModelAdmin): | ||||||
|   | |||||||
| @@ -292,6 +292,7 @@ class StockItemAdmin(ImportExportModelAdmin): | |||||||
|         'sales_order', |         'sales_order', | ||||||
|         'stocktake_user', |         'stocktake_user', | ||||||
|         'supplier_part', |         'supplier_part', | ||||||
|  |         'consumed_by', | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,13 +4,13 @@ import { ReactNode } from 'react'; | |||||||
| import { notYetImplemented } from '../../functions/notifications'; | import { notYetImplemented } from '../../functions/notifications'; | ||||||
|  |  | ||||||
| export type ActionButtonProps = { | export type ActionButtonProps = { | ||||||
|   key?: string; |  | ||||||
|   icon?: ReactNode; |   icon?: ReactNode; | ||||||
|   text?: string; |   text?: string; | ||||||
|   color?: string; |   color?: string; | ||||||
|   tooltip?: string; |   tooltip?: string; | ||||||
|   variant?: string; |   variant?: string; | ||||||
|   size?: number | string; |   size?: number | string; | ||||||
|  |   radius?: number | string; | ||||||
|   disabled?: boolean; |   disabled?: boolean; | ||||||
|   onClick?: any; |   onClick?: any; | ||||||
|   hidden?: boolean; |   hidden?: boolean; | ||||||
| @@ -26,15 +26,16 @@ export function ActionButton(props: ActionButtonProps) { | |||||||
|   return ( |   return ( | ||||||
|     !hidden && ( |     !hidden && ( | ||||||
|       <Tooltip |       <Tooltip | ||||||
|         key={`tooltip-${props.key}`} |         key={`tooltip-${props.text}`} | ||||||
|         disabled={!props.tooltip && !props.text} |         disabled={!props.tooltip && !props.text} | ||||||
|         label={props.tooltip ?? props.text} |         label={props.tooltip ?? props.text} | ||||||
|         position={props.tooltipAlignment ?? 'left'} |         position={props.tooltipAlignment ?? 'left'} | ||||||
|       > |       > | ||||||
|         <ActionIcon |         <ActionIcon | ||||||
|           key={`action-icon-${props.key}`} |           key={`action-icon-${props.text}`} | ||||||
|           disabled={props.disabled} |           disabled={props.disabled} | ||||||
|           radius="xs" |           p={17} | ||||||
|  |           radius={props.radius ?? 'xs'} | ||||||
|           color={props.color} |           color={props.color} | ||||||
|           size={props.size} |           size={props.size} | ||||||
|           onClick={props.onClick ?? notYetImplemented} |           onClick={props.onClick ?? notYetImplemented} | ||||||
|   | |||||||
							
								
								
									
										88
									
								
								src/frontend/src/components/buttons/AdminButton.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/frontend/src/components/buttons/AdminButton.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | import { t } from '@lingui/macro'; | ||||||
|  | import { IconUserStar } from '@tabler/icons-react'; | ||||||
|  | import { useCallback, useMemo } from 'react'; | ||||||
|  | import { useNavigate } from 'react-router-dom'; | ||||||
|  |  | ||||||
|  | import { ModelType } from '../../enums/ModelType'; | ||||||
|  | import { navigateToLink } from '../../functions/navigation'; | ||||||
|  | import { base_url } from '../../main'; | ||||||
|  | import { useLocalState } from '../../states/LocalState'; | ||||||
|  | import { useUserState } from '../../states/UserState'; | ||||||
|  | import { ModelInformationDict } from '../render/ModelType'; | ||||||
|  | import { ActionButton } from './ActionButton'; | ||||||
|  |  | ||||||
|  | export type AdminButtonProps = { | ||||||
|  |   model: ModelType; | ||||||
|  |   pk: number | undefined; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * A button that is used to navigate to the admin page for the selected item. | ||||||
|  |  * | ||||||
|  |  * This button is only rendered if: | ||||||
|  |  * - The admin interface is enabled for the server | ||||||
|  |  * - The selected model has an associated admin URL | ||||||
|  |  * - The user has "superuser" role | ||||||
|  |  * - The user has at least read rights for the selected item | ||||||
|  |  */ | ||||||
|  | export default function AdminButton(props: AdminButtonProps) { | ||||||
|  |   const user = useUserState(); | ||||||
|  |  | ||||||
|  |   const enabled: boolean = useMemo(() => { | ||||||
|  |     // Only users with superuser permission will see this button | ||||||
|  |     if (!user || !user.isLoggedIn() || !user.isSuperuser()) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // TODO: Check if the server has the admin interface enabled | ||||||
|  |  | ||||||
|  |     const modelDef = ModelInformationDict[props.model]; | ||||||
|  |  | ||||||
|  |     // No admin URL associated with the model | ||||||
|  |     if (!modelDef.admin_url) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // No primary key provided | ||||||
|  |     if (!props.pk) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return true; | ||||||
|  |   }, [user, props.model, props.pk]); | ||||||
|  |  | ||||||
|  |   const openAdmin = useCallback( | ||||||
|  |     (event: any) => { | ||||||
|  |       const modelDef = ModelInformationDict[props.model]; | ||||||
|  |       const host = useLocalState.getState().host; | ||||||
|  |  | ||||||
|  |       if (!modelDef.admin_url) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // TODO: Check the actual "admin" URL (it may be custom) | ||||||
|  |       const url = `${host}/admin${modelDef.admin_url}${props.pk}/`; | ||||||
|  |  | ||||||
|  |       if (event?.ctrlKey || event?.shiftKey) { | ||||||
|  |         // Open the link in a new tab | ||||||
|  |         window.open(url, '_blank'); | ||||||
|  |       } else { | ||||||
|  |         window.open(url, '_self'); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     [props.model, props.pk] | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <ActionButton | ||||||
|  |       icon={<IconUserStar />} | ||||||
|  |       color="blue" | ||||||
|  |       size="lg" | ||||||
|  |       radius="sm" | ||||||
|  |       variant="filled" | ||||||
|  |       tooltip={t`Open in admin interface`} | ||||||
|  |       hidden={!enabled} | ||||||
|  |       onClick={openAdmin} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | } | ||||||
| @@ -10,6 +10,7 @@ export interface ModelInformationInterface { | |||||||
|   url_detail?: string; |   url_detail?: string; | ||||||
|   api_endpoint: ApiEndpoints; |   api_endpoint: ApiEndpoints; | ||||||
|   cui_detail?: string; |   cui_detail?: string; | ||||||
|  |   admin_url?: string; | ||||||
| } | } | ||||||
|  |  | ||||||
| export type ModelDict = { | export type ModelDict = { | ||||||
| @@ -23,7 +24,8 @@ export const ModelInformationDict: ModelDict = { | |||||||
|     url_overview: '/part', |     url_overview: '/part', | ||||||
|     url_detail: '/part/:pk/', |     url_detail: '/part/:pk/', | ||||||
|     cui_detail: '/part/:pk/', |     cui_detail: '/part/:pk/', | ||||||
|     api_endpoint: ApiEndpoints.part_list |     api_endpoint: ApiEndpoints.part_list, | ||||||
|  |     admin_url: '/part/part/' | ||||||
|   }, |   }, | ||||||
|   partparametertemplate: { |   partparametertemplate: { | ||||||
|     label: t`Part Parameter Template`, |     label: t`Part Parameter Template`, | ||||||
| @@ -45,7 +47,8 @@ export const ModelInformationDict: ModelDict = { | |||||||
|     url_overview: '/supplierpart', |     url_overview: '/supplierpart', | ||||||
|     url_detail: '/purchasing/supplier-part/:pk/', |     url_detail: '/purchasing/supplier-part/:pk/', | ||||||
|     cui_detail: '/supplier-part/:pk/', |     cui_detail: '/supplier-part/:pk/', | ||||||
|     api_endpoint: ApiEndpoints.supplier_part_list |     api_endpoint: ApiEndpoints.supplier_part_list, | ||||||
|  |     admin_url: '/company/supplierpart/' | ||||||
|   }, |   }, | ||||||
|   manufacturerpart: { |   manufacturerpart: { | ||||||
|     label: t`Manufacturer Part`, |     label: t`Manufacturer Part`, | ||||||
| @@ -53,7 +56,8 @@ export const ModelInformationDict: ModelDict = { | |||||||
|     url_overview: '/manufacturerpart', |     url_overview: '/manufacturerpart', | ||||||
|     url_detail: '/purchasing/manufacturer-part/:pk/', |     url_detail: '/purchasing/manufacturer-part/:pk/', | ||||||
|     cui_detail: '/manufacturer-part/:pk/', |     cui_detail: '/manufacturer-part/:pk/', | ||||||
|     api_endpoint: ApiEndpoints.manufacturer_part_list |     api_endpoint: ApiEndpoints.manufacturer_part_list, | ||||||
|  |     admin_url: '/company/manufacturerpart/' | ||||||
|   }, |   }, | ||||||
|   partcategory: { |   partcategory: { | ||||||
|     label: t`Part Category`, |     label: t`Part Category`, | ||||||
| @@ -61,7 +65,8 @@ export const ModelInformationDict: ModelDict = { | |||||||
|     url_overview: '/part/category', |     url_overview: '/part/category', | ||||||
|     url_detail: '/part/category/:pk/', |     url_detail: '/part/category/:pk/', | ||||||
|     cui_detail: '/part/category/:pk/', |     cui_detail: '/part/category/:pk/', | ||||||
|     api_endpoint: ApiEndpoints.category_list |     api_endpoint: ApiEndpoints.category_list, | ||||||
|  |     admin_url: '/part/partcategory/' | ||||||
|   }, |   }, | ||||||
|   stockitem: { |   stockitem: { | ||||||
|     label: t`Stock Item`, |     label: t`Stock Item`, | ||||||
| @@ -69,7 +74,8 @@ export const ModelInformationDict: ModelDict = { | |||||||
|     url_overview: '/stock/item', |     url_overview: '/stock/item', | ||||||
|     url_detail: '/stock/item/:pk/', |     url_detail: '/stock/item/:pk/', | ||||||
|     cui_detail: '/stock/item/:pk/', |     cui_detail: '/stock/item/:pk/', | ||||||
|     api_endpoint: ApiEndpoints.stock_item_list |     api_endpoint: ApiEndpoints.stock_item_list, | ||||||
|  |     admin_url: '/stock/stockitem/' | ||||||
|   }, |   }, | ||||||
|   stocklocation: { |   stocklocation: { | ||||||
|     label: t`Stock Location`, |     label: t`Stock Location`, | ||||||
| @@ -77,7 +83,8 @@ export const ModelInformationDict: ModelDict = { | |||||||
|     url_overview: '/stock/location', |     url_overview: '/stock/location', | ||||||
|     url_detail: '/stock/location/:pk/', |     url_detail: '/stock/location/:pk/', | ||||||
|     cui_detail: '/stock/location/:pk/', |     cui_detail: '/stock/location/:pk/', | ||||||
|     api_endpoint: ApiEndpoints.stock_location_list |     api_endpoint: ApiEndpoints.stock_location_list, | ||||||
|  |     admin_url: '/stock/stocklocation/' | ||||||
|   }, |   }, | ||||||
|   stocklocationtype: { |   stocklocationtype: { | ||||||
|     label: t`Stock Location Type`, |     label: t`Stock Location Type`, | ||||||
| @@ -95,7 +102,8 @@ export const ModelInformationDict: ModelDict = { | |||||||
|     url_overview: '/build', |     url_overview: '/build', | ||||||
|     url_detail: '/build/:pk/', |     url_detail: '/build/:pk/', | ||||||
|     cui_detail: '/build/:pk/', |     cui_detail: '/build/:pk/', | ||||||
|     api_endpoint: ApiEndpoints.build_order_list |     api_endpoint: ApiEndpoints.build_order_list, | ||||||
|  |     admin_url: '/build/build/' | ||||||
|   }, |   }, | ||||||
|   buildline: { |   buildline: { | ||||||
|     label: t`Build Line`, |     label: t`Build Line`, | ||||||
| @@ -111,7 +119,8 @@ export const ModelInformationDict: ModelDict = { | |||||||
|     url_overview: '/company', |     url_overview: '/company', | ||||||
|     url_detail: '/company/:pk/', |     url_detail: '/company/:pk/', | ||||||
|     cui_detail: '/company/:pk/', |     cui_detail: '/company/:pk/', | ||||||
|     api_endpoint: ApiEndpoints.company_list |     api_endpoint: ApiEndpoints.company_list, | ||||||
|  |     admin_url: '/company/company/' | ||||||
|   }, |   }, | ||||||
|   projectcode: { |   projectcode: { | ||||||
|     label: t`Project Code`, |     label: t`Project Code`, | ||||||
| @@ -126,7 +135,8 @@ export const ModelInformationDict: ModelDict = { | |||||||
|     url_overview: '/purchasing/purchase-order', |     url_overview: '/purchasing/purchase-order', | ||||||
|     url_detail: '/purchasing/purchase-order/:pk/', |     url_detail: '/purchasing/purchase-order/:pk/', | ||||||
|     cui_detail: '/order/purchase-order/:pk/', |     cui_detail: '/order/purchase-order/:pk/', | ||||||
|     api_endpoint: ApiEndpoints.purchase_order_list |     api_endpoint: ApiEndpoints.purchase_order_list, | ||||||
|  |     admin_url: '/order/purchaseorder/' | ||||||
|   }, |   }, | ||||||
|   purchaseorderline: { |   purchaseorderline: { | ||||||
|     label: t`Purchase Order Line`, |     label: t`Purchase Order Line`, | ||||||
| @@ -139,7 +149,8 @@ export const ModelInformationDict: ModelDict = { | |||||||
|     url_overview: '/sales/sales-order', |     url_overview: '/sales/sales-order', | ||||||
|     url_detail: '/sales/sales-order/:pk/', |     url_detail: '/sales/sales-order/:pk/', | ||||||
|     cui_detail: '/order/sales-order/:pk/', |     cui_detail: '/order/sales-order/:pk/', | ||||||
|     api_endpoint: ApiEndpoints.sales_order_list |     api_endpoint: ApiEndpoints.sales_order_list, | ||||||
|  |     admin_url: '/order/salesorder/' | ||||||
|   }, |   }, | ||||||
|   salesordershipment: { |   salesordershipment: { | ||||||
|     label: t`Sales Order Shipment`, |     label: t`Sales Order Shipment`, | ||||||
| @@ -154,7 +165,8 @@ export const ModelInformationDict: ModelDict = { | |||||||
|     url_overview: '/sales/return-order', |     url_overview: '/sales/return-order', | ||||||
|     url_detail: '/sales/return-order/:pk/', |     url_detail: '/sales/return-order/:pk/', | ||||||
|     cui_detail: '/order/return-order/:pk/', |     cui_detail: '/order/return-order/:pk/', | ||||||
|     api_endpoint: ApiEndpoints.return_order_list |     api_endpoint: ApiEndpoints.return_order_list, | ||||||
|  |     admin_url: '/order/returnorder/' | ||||||
|   }, |   }, | ||||||
|   address: { |   address: { | ||||||
|     label: t`Address`, |     label: t`Address`, | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ import { | |||||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||||
| import { useParams } from 'react-router-dom'; | import { useParams } from 'react-router-dom'; | ||||||
|  |  | ||||||
|  | import AdminButton from '../../components/buttons/AdminButton'; | ||||||
| import { DetailsField, DetailsTable } from '../../components/details/Details'; | import { DetailsField, DetailsTable } from '../../components/details/Details'; | ||||||
| import { DetailsImage } from '../../components/details/DetailsImage'; | import { DetailsImage } from '../../components/details/DetailsImage'; | ||||||
| import { ItemDetailsGrid } from '../../components/details/ItemDetails'; | import { ItemDetailsGrid } from '../../components/details/ItemDetails'; | ||||||
| @@ -347,8 +348,8 @@ export default function BuildDetail() { | |||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   const buildActions = useMemo(() => { |   const buildActions = useMemo(() => { | ||||||
|     // TODO: Disable certain actions based on user permissions |  | ||||||
|     return [ |     return [ | ||||||
|  |       <AdminButton model={ModelType.build} pk={build.pk} />, | ||||||
|       <ActionDropdown |       <ActionDropdown | ||||||
|         key="barcode" |         key="barcode" | ||||||
|         tooltip={t`Barcode Actions`} |         tooltip={t`Barcode Actions`} | ||||||
| @@ -386,7 +387,8 @@ export default function BuildDetail() { | |||||||
|           }), |           }), | ||||||
|           CancelItemAction({ |           CancelItemAction({ | ||||||
|             tooltip: t`Cancel order`, |             tooltip: t`Cancel order`, | ||||||
|             onClick: () => cancelBuild.open() |             onClick: () => cancelBuild.open(), | ||||||
|  |             hidden: !user.hasChangeRole(UserRoles.build) | ||||||
|             // TODO: Hide if build cannot be cancelled |             // TODO: Hide if build cannot be cancelled | ||||||
|           }), |           }), | ||||||
|           DuplicateItemAction({ |           DuplicateItemAction({ | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ import { | |||||||
| import { ReactNode, useMemo } from 'react'; | import { ReactNode, useMemo } from 'react'; | ||||||
| import { useParams } from 'react-router-dom'; | import { useParams } from 'react-router-dom'; | ||||||
|  |  | ||||||
|  | import AdminButton from '../../components/buttons/AdminButton'; | ||||||
| import { DetailsField, DetailsTable } from '../../components/details/Details'; | import { DetailsField, DetailsTable } from '../../components/details/Details'; | ||||||
| import DetailsBadge from '../../components/details/DetailsBadge'; | import DetailsBadge from '../../components/details/DetailsBadge'; | ||||||
| import { DetailsImage } from '../../components/details/DetailsImage'; | import { DetailsImage } from '../../components/details/DetailsImage'; | ||||||
| @@ -32,6 +33,7 @@ import { PageDetail } from '../../components/nav/PageDetail'; | |||||||
| import { PanelGroup, PanelType } from '../../components/nav/PanelGroup'; | import { PanelGroup, PanelType } from '../../components/nav/PanelGroup'; | ||||||
| import { NotesEditor } from '../../components/widgets/MarkdownEditor'; | import { NotesEditor } from '../../components/widgets/MarkdownEditor'; | ||||||
| import { ApiEndpoints } from '../../enums/ApiEndpoints'; | import { ApiEndpoints } from '../../enums/ApiEndpoints'; | ||||||
|  | import { ModelType } from '../../enums/ModelType'; | ||||||
| import { UserRoles } from '../../enums/Roles'; | import { UserRoles } from '../../enums/Roles'; | ||||||
| import { companyFields } from '../../forms/CompanyForms'; | import { companyFields } from '../../forms/CompanyForms'; | ||||||
| import { useEditApiFormModal } from '../../hooks/UseForm'; | import { useEditApiFormModal } from '../../hooks/UseForm'; | ||||||
| @@ -285,6 +287,7 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) { | |||||||
|  |  | ||||||
|   const companyActions = useMemo(() => { |   const companyActions = useMemo(() => { | ||||||
|     return [ |     return [ | ||||||
|  |       <AdminButton model={ModelType.company} pk={company.pk} />, | ||||||
|       <ActionDropdown |       <ActionDropdown | ||||||
|         key="company" |         key="company" | ||||||
|         tooltip={t`Company Actions`} |         tooltip={t`Company Actions`} | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import { | |||||||
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||||
| import { useParams } from 'react-router-dom'; | import { useParams } from 'react-router-dom'; | ||||||
|  |  | ||||||
|  | import AdminButton from '../../components/buttons/AdminButton'; | ||||||
| import { DetailsField, DetailsTable } from '../../components/details/Details'; | import { DetailsField, DetailsTable } from '../../components/details/Details'; | ||||||
| import { DetailsImage } from '../../components/details/DetailsImage'; | import { DetailsImage } from '../../components/details/DetailsImage'; | ||||||
| import { ItemDetailsGrid } from '../../components/details/ItemDetails'; | import { ItemDetailsGrid } from '../../components/details/ItemDetails'; | ||||||
| @@ -204,6 +205,10 @@ export default function ManufacturerPartDetail() { | |||||||
|  |  | ||||||
|   const manufacturerPartActions = useMemo(() => { |   const manufacturerPartActions = useMemo(() => { | ||||||
|     return [ |     return [ | ||||||
|  |       <AdminButton | ||||||
|  |         model={ModelType.manufacturerpart} | ||||||
|  |         pk={manufacturerPart.pk} | ||||||
|  |       />, | ||||||
|       <ActionDropdown |       <ActionDropdown | ||||||
|         key="part" |         key="part" | ||||||
|         tooltip={t`Manufacturer Part Actions`} |         tooltip={t`Manufacturer Part Actions`} | ||||||
| @@ -223,7 +228,7 @@ export default function ManufacturerPartDetail() { | |||||||
|         ]} |         ]} | ||||||
|       /> |       /> | ||||||
|     ]; |     ]; | ||||||
|   }, [user]); |   }, [user, manufacturerPart]); | ||||||
|  |  | ||||||
|   const breadcrumbs = useMemo(() => { |   const breadcrumbs = useMemo(() => { | ||||||
|     return [ |     return [ | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import { | |||||||
| import { ReactNode, useMemo } from 'react'; | import { ReactNode, useMemo } from 'react'; | ||||||
| import { useParams } from 'react-router-dom'; | import { useParams } from 'react-router-dom'; | ||||||
|  |  | ||||||
|  | import AdminButton from '../../components/buttons/AdminButton'; | ||||||
| import { DetailsField, DetailsTable } from '../../components/details/Details'; | import { DetailsField, DetailsTable } from '../../components/details/Details'; | ||||||
| import DetailsBadge from '../../components/details/DetailsBadge'; | import DetailsBadge from '../../components/details/DetailsBadge'; | ||||||
| import { DetailsImage } from '../../components/details/DetailsImage'; | import { DetailsImage } from '../../components/details/DetailsImage'; | ||||||
| @@ -243,6 +244,7 @@ export default function SupplierPartDetail() { | |||||||
|  |  | ||||||
|   const supplierPartActions = useMemo(() => { |   const supplierPartActions = useMemo(() => { | ||||||
|     return [ |     return [ | ||||||
|  |       <AdminButton model={ModelType.supplierpart} pk={supplierPart.pk} />, | ||||||
|       <ActionDropdown |       <ActionDropdown | ||||||
|         key="part" |         key="part" | ||||||
|         tooltip={t`Supplier Part Actions`} |         tooltip={t`Supplier Part Actions`} | ||||||
| @@ -262,7 +264,7 @@ export default function SupplierPartDetail() { | |||||||
|         ]} |         ]} | ||||||
|       /> |       /> | ||||||
|     ]; |     ]; | ||||||
|   }, [user]); |   }, [user, supplierPart]); | ||||||
|  |  | ||||||
|   const supplierPartFields = useSupplierPartFields(); |   const supplierPartFields = useSupplierPartFields(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import { | |||||||
| import { useMemo, useState } from 'react'; | import { useMemo, useState } from 'react'; | ||||||
| import { useNavigate, useParams } from 'react-router-dom'; | import { useNavigate, useParams } from 'react-router-dom'; | ||||||
|  |  | ||||||
|  | import AdminButton from '../../components/buttons/AdminButton'; | ||||||
| import { DetailsField, DetailsTable } from '../../components/details/Details'; | import { DetailsField, DetailsTable } from '../../components/details/Details'; | ||||||
| import { ItemDetailsGrid } from '../../components/details/ItemDetails'; | import { ItemDetailsGrid } from '../../components/details/ItemDetails'; | ||||||
| import { | import { | ||||||
| @@ -201,6 +202,7 @@ export default function CategoryDetail({}: {}) { | |||||||
|  |  | ||||||
|   const categoryActions = useMemo(() => { |   const categoryActions = useMemo(() => { | ||||||
|     return [ |     return [ | ||||||
|  |       <AdminButton model={ModelType.partcategory} pk={category.pk} />, | ||||||
|       <ActionDropdown |       <ActionDropdown | ||||||
|         key="category" |         key="category" | ||||||
|         tooltip={t`Category Actions`} |         tooltip={t`Category Actions`} | ||||||
| @@ -219,7 +221,7 @@ export default function CategoryDetail({}: {}) { | |||||||
|         ]} |         ]} | ||||||
|       /> |       /> | ||||||
|     ]; |     ]; | ||||||
|   }, [id, user]); |   }, [id, user, category.pk]); | ||||||
|  |  | ||||||
|   const categoryPanels: PanelType[] = useMemo( |   const categoryPanels: PanelType[] = useMemo( | ||||||
|     () => [ |     () => [ | ||||||
|   | |||||||
| @@ -34,6 +34,7 @@ import { ReactNode, useMemo, useState } from 'react'; | |||||||
| import { useNavigate, useParams } from 'react-router-dom'; | import { useNavigate, useParams } from 'react-router-dom'; | ||||||
|  |  | ||||||
| import { api } from '../../App'; | import { api } from '../../App'; | ||||||
|  | import AdminButton from '../../components/buttons/AdminButton'; | ||||||
| import { DetailsField, DetailsTable } from '../../components/details/Details'; | import { DetailsField, DetailsTable } from '../../components/details/Details'; | ||||||
| import DetailsBadge from '../../components/details/DetailsBadge'; | import DetailsBadge from '../../components/details/DetailsBadge'; | ||||||
| import { DetailsImage } from '../../components/details/DetailsImage'; | import { DetailsImage } from '../../components/details/DetailsImage'; | ||||||
| @@ -746,6 +747,7 @@ export default function PartDetail() { | |||||||
|  |  | ||||||
|   const partActions = useMemo(() => { |   const partActions = useMemo(() => { | ||||||
|     return [ |     return [ | ||||||
|  |       <AdminButton model={ModelType.part} pk={part.pk} />, | ||||||
|       <BarcodeActionDropdown |       <BarcodeActionDropdown | ||||||
|         actions={[ |         actions={[ | ||||||
|           ViewBarcodeAction({}), |           ViewBarcodeAction({}), | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import { | |||||||
| import { ReactNode, useMemo } from 'react'; | import { ReactNode, useMemo } from 'react'; | ||||||
| import { useParams } from 'react-router-dom'; | import { useParams } from 'react-router-dom'; | ||||||
|  |  | ||||||
|  | import AdminButton from '../../components/buttons/AdminButton'; | ||||||
| import { DetailsField, DetailsTable } from '../../components/details/Details'; | import { DetailsField, DetailsTable } from '../../components/details/Details'; | ||||||
| import { DetailsImage } from '../../components/details/DetailsImage'; | import { DetailsImage } from '../../components/details/DetailsImage'; | ||||||
| import { ItemDetailsGrid } from '../../components/details/ItemDetails'; | import { ItemDetailsGrid } from '../../components/details/ItemDetails'; | ||||||
| @@ -300,6 +301,7 @@ export default function PurchaseOrderDetail() { | |||||||
|  |  | ||||||
|   const poActions = useMemo(() => { |   const poActions = useMemo(() => { | ||||||
|     return [ |     return [ | ||||||
|  |       <AdminButton model={ModelType.purchaseorder} pk={order.pk} />, | ||||||
|       <BarcodeActionDropdown |       <BarcodeActionDropdown | ||||||
|         actions={[ |         actions={[ | ||||||
|           ViewBarcodeAction({}), |           ViewBarcodeAction({}), | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import { | |||||||
| import { ReactNode, useMemo } from 'react'; | import { ReactNode, useMemo } from 'react'; | ||||||
| import { useParams } from 'react-router-dom'; | import { useParams } from 'react-router-dom'; | ||||||
|  |  | ||||||
|  | import AdminButton from '../../components/buttons/AdminButton'; | ||||||
| import { DetailsField, DetailsTable } from '../../components/details/Details'; | import { DetailsField, DetailsTable } from '../../components/details/Details'; | ||||||
| import { DetailsImage } from '../../components/details/DetailsImage'; | import { DetailsImage } from '../../components/details/DetailsImage'; | ||||||
| import { ItemDetailsGrid } from '../../components/details/ItemDetails'; | import { ItemDetailsGrid } from '../../components/details/ItemDetails'; | ||||||
| @@ -285,6 +286,7 @@ export default function ReturnOrderDetail() { | |||||||
|  |  | ||||||
|   const orderActions = useMemo(() => { |   const orderActions = useMemo(() => { | ||||||
|     return [ |     return [ | ||||||
|  |       <AdminButton model={ModelType.returnorder} pk={order.pk} />, | ||||||
|       <ActionDropdown |       <ActionDropdown | ||||||
|         key="order-actions" |         key="order-actions" | ||||||
|         tooltip={t`Order Actions`} |         tooltip={t`Order Actions`} | ||||||
| @@ -306,7 +308,7 @@ export default function ReturnOrderDetail() { | |||||||
|         ]} |         ]} | ||||||
|       /> |       /> | ||||||
|     ]; |     ]; | ||||||
|   }, [user]); |   }, [user, order]); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ import { | |||||||
| import { ReactNode, useMemo } from 'react'; | import { ReactNode, useMemo } from 'react'; | ||||||
| import { useParams } from 'react-router-dom'; | import { useParams } from 'react-router-dom'; | ||||||
|  |  | ||||||
|  | import AdminButton from '../../components/buttons/AdminButton'; | ||||||
| import { DetailsField, DetailsTable } from '../../components/details/Details'; | import { DetailsField, DetailsTable } from '../../components/details/Details'; | ||||||
| import { DetailsImage } from '../../components/details/DetailsImage'; | import { DetailsImage } from '../../components/details/DetailsImage'; | ||||||
| import { ItemDetailsGrid } from '../../components/details/ItemDetails'; | import { ItemDetailsGrid } from '../../components/details/ItemDetails'; | ||||||
| @@ -297,6 +298,7 @@ export default function SalesOrderDetail() { | |||||||
|  |  | ||||||
|   const soActions = useMemo(() => { |   const soActions = useMemo(() => { | ||||||
|     return [ |     return [ | ||||||
|  |       <AdminButton model={ModelType.salesorder} pk={order.pk} />, | ||||||
|       <ActionDropdown |       <ActionDropdown | ||||||
|         key="order-actions" |         key="order-actions" | ||||||
|         tooltip={t`Order Actions`} |         tooltip={t`Order Actions`} | ||||||
| @@ -316,7 +318,7 @@ export default function SalesOrderDetail() { | |||||||
|         ]} |         ]} | ||||||
|       /> |       /> | ||||||
|     ]; |     ]; | ||||||
|   }, [user]); |   }, [user, order]); | ||||||
|  |  | ||||||
|   const orderBadges: ReactNode[] = useMemo(() => { |   const orderBadges: ReactNode[] = useMemo(() => { | ||||||
|     return instanceQuery.isLoading |     return instanceQuery.isLoading | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import { useMemo, useState } from 'react'; | |||||||
| import { useNavigate, useParams } from 'react-router-dom'; | import { useNavigate, useParams } from 'react-router-dom'; | ||||||
|  |  | ||||||
| import { ActionButton } from '../../components/buttons/ActionButton'; | import { ActionButton } from '../../components/buttons/ActionButton'; | ||||||
|  | import AdminButton from '../../components/buttons/AdminButton'; | ||||||
| import { DetailsField, DetailsTable } from '../../components/details/Details'; | import { DetailsField, DetailsTable } from '../../components/details/Details'; | ||||||
| import { ItemDetailsGrid } from '../../components/details/ItemDetails'; | import { ItemDetailsGrid } from '../../components/details/ItemDetails'; | ||||||
| import { | import { | ||||||
| @@ -266,6 +267,7 @@ export default function Stock() { | |||||||
|  |  | ||||||
|   const locationActions = useMemo( |   const locationActions = useMemo( | ||||||
|     () => [ |     () => [ | ||||||
|  |       <AdminButton model={ModelType.stocklocation} pk={location.pk} />, | ||||||
|       <ActionButton |       <ActionButton | ||||||
|         icon={<InvenTreeIcon icon="stocktake" />} |         icon={<InvenTreeIcon icon="stocktake" />} | ||||||
|         variant="outline" |         variant="outline" | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ import { | |||||||
| import { ReactNode, useMemo, useState } from 'react'; | import { ReactNode, useMemo, useState } from 'react'; | ||||||
| import { useNavigate, useParams } from 'react-router-dom'; | import { useNavigate, useParams } from 'react-router-dom'; | ||||||
|  |  | ||||||
|  | import AdminButton from '../../components/buttons/AdminButton'; | ||||||
| import { DetailsField, DetailsTable } from '../../components/details/Details'; | import { DetailsField, DetailsTable } from '../../components/details/Details'; | ||||||
| import DetailsBadge from '../../components/details/DetailsBadge'; | import DetailsBadge from '../../components/details/DetailsBadge'; | ||||||
| import { DetailsImage } from '../../components/details/DetailsImage'; | import { DetailsImage } from '../../components/details/DetailsImage'; | ||||||
| @@ -408,6 +409,7 @@ export default function StockDetail() { | |||||||
|  |  | ||||||
|   const stockActions = useMemo( |   const stockActions = useMemo( | ||||||
|     () => [ |     () => [ | ||||||
|  |       <AdminButton model={ModelType.stockitem} pk={stockitem.pk} />, | ||||||
|       <BarcodeActionDropdown |       <BarcodeActionDropdown | ||||||
|         actions={[ |         actions={[ | ||||||
|           ViewBarcodeAction({}), |           ViewBarcodeAction({}), | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user