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:
Oliver 2024-05-17 12:25:47 +10:00 committed by GitHub
parent acb1ec4c83
commit dc741b6183
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 169 additions and 26 deletions

View File

@ -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."""

View File

@ -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']

View File

@ -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):

View File

@ -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):

View File

@ -292,6 +292,7 @@ class StockItemAdmin(ImportExportModelAdmin):
'sales_order', 'sales_order',
'stocktake_user', 'stocktake_user',
'supplier_part', 'supplier_part',
'consumed_by',
] ]

View File

@ -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}

View 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}
/>
);
}

View File

@ -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`,

View File

@ -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({

View File

@ -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`}

View File

@ -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 [

View File

@ -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();

View File

@ -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(
() => [ () => [

View File

@ -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({}),

View File

@ -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({}),

View File

@ -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 (
<> <>

View File

@ -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

View File

@ -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"

View File

@ -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({}),