mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +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:
parent
acb1ec4c83
commit
dc741b6183
@ -7,6 +7,15 @@ from import_export.admin import ImportExportModelAdmin
|
||||
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):
|
||||
"""Admin settings for InvenTreeSetting."""
|
||||
|
||||
|
@ -213,6 +213,8 @@ class AddressAdmin(ImportExportModelAdmin):
|
||||
|
||||
search_fields = ['company', 'country', 'postal_code']
|
||||
|
||||
autocomplete_fields = ['company']
|
||||
|
||||
|
||||
class ContactResource(InvenTreeResource):
|
||||
"""Class for managing Contact data import/export."""
|
||||
@ -237,3 +239,5 @@ class ContactAdmin(ImportExportModelAdmin):
|
||||
list_display = ('company', 'name', 'role', 'email', 'phone')
|
||||
|
||||
search_fields = ['company', 'name', 'email']
|
||||
|
||||
autocomplete_fields = ['company']
|
||||
|
@ -114,7 +114,7 @@ class PurchaseOrderAdmin(ImportExportModelAdmin):
|
||||
|
||||
inlines = [PurchaseOrderLineItemInlineAdmin]
|
||||
|
||||
autocomplete_fields = ('supplier',)
|
||||
autocomplete_fields = ['supplier', 'project_code', 'contact', 'address']
|
||||
|
||||
|
||||
class SalesOrderResource(
|
||||
@ -152,7 +152,7 @@ class SalesOrderAdmin(ImportExportModelAdmin):
|
||||
|
||||
search_fields = ['reference', 'customer__name', 'description']
|
||||
|
||||
autocomplete_fields = ('customer',)
|
||||
autocomplete_fields = ['customer', 'project_code', 'contact', 'address']
|
||||
|
||||
|
||||
class PurchaseOrderLineItemResource(PriceResourceMixin, InvenTreeResource):
|
||||
@ -317,7 +317,7 @@ class ReturnOrderAdmin(ImportExportModelAdmin):
|
||||
|
||||
search_fields = ['reference', 'customer__name', 'description']
|
||||
|
||||
autocomplete_fields = ['customer']
|
||||
autocomplete_fields = ['customer', 'project_code', 'contact', 'address']
|
||||
|
||||
|
||||
class ReturnOrderLineItemResource(PriceResourceMixin, InvenTreeResource):
|
||||
|
@ -250,6 +250,8 @@ class PartAdmin(ImportExportModelAdmin):
|
||||
'category',
|
||||
'default_location',
|
||||
'default_supplier',
|
||||
'bom_checked_by',
|
||||
'creation_user',
|
||||
]
|
||||
|
||||
inlines = [PartParameterInline]
|
||||
@ -260,7 +262,7 @@ class PartPricingAdmin(admin.ModelAdmin):
|
||||
|
||||
list_display = ('part', 'overall_min', 'overall_max')
|
||||
|
||||
autcomplete_fields = ['part']
|
||||
autocomplete_fields = ['part']
|
||||
|
||||
|
||||
class PartStocktakeAdmin(admin.ModelAdmin):
|
||||
|
@ -292,6 +292,7 @@ class StockItemAdmin(ImportExportModelAdmin):
|
||||
'sales_order',
|
||||
'stocktake_user',
|
||||
'supplier_part',
|
||||
'consumed_by',
|
||||
]
|
||||
|
||||
|
||||
|
@ -4,13 +4,13 @@ import { ReactNode } from 'react';
|
||||
import { notYetImplemented } from '../../functions/notifications';
|
||||
|
||||
export type ActionButtonProps = {
|
||||
key?: string;
|
||||
icon?: ReactNode;
|
||||
text?: string;
|
||||
color?: string;
|
||||
tooltip?: string;
|
||||
variant?: string;
|
||||
size?: number | string;
|
||||
radius?: number | string;
|
||||
disabled?: boolean;
|
||||
onClick?: any;
|
||||
hidden?: boolean;
|
||||
@ -26,15 +26,16 @@ export function ActionButton(props: ActionButtonProps) {
|
||||
return (
|
||||
!hidden && (
|
||||
<Tooltip
|
||||
key={`tooltip-${props.key}`}
|
||||
key={`tooltip-${props.text}`}
|
||||
disabled={!props.tooltip && !props.text}
|
||||
label={props.tooltip ?? props.text}
|
||||
position={props.tooltipAlignment ?? 'left'}
|
||||
>
|
||||
<ActionIcon
|
||||
key={`action-icon-${props.key}`}
|
||||
key={`action-icon-${props.text}`}
|
||||
disabled={props.disabled}
|
||||
radius="xs"
|
||||
p={17}
|
||||
radius={props.radius ?? 'xs'}
|
||||
color={props.color}
|
||||
size={props.size}
|
||||
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;
|
||||
api_endpoint: ApiEndpoints;
|
||||
cui_detail?: string;
|
||||
admin_url?: string;
|
||||
}
|
||||
|
||||
export type ModelDict = {
|
||||
@ -23,7 +24,8 @@ export const ModelInformationDict: ModelDict = {
|
||||
url_overview: '/part',
|
||||
url_detail: '/part/:pk/',
|
||||
cui_detail: '/part/:pk/',
|
||||
api_endpoint: ApiEndpoints.part_list
|
||||
api_endpoint: ApiEndpoints.part_list,
|
||||
admin_url: '/part/part/'
|
||||
},
|
||||
partparametertemplate: {
|
||||
label: t`Part Parameter Template`,
|
||||
@ -45,7 +47,8 @@ export const ModelInformationDict: ModelDict = {
|
||||
url_overview: '/supplierpart',
|
||||
url_detail: '/purchasing/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: {
|
||||
label: t`Manufacturer Part`,
|
||||
@ -53,7 +56,8 @@ export const ModelInformationDict: ModelDict = {
|
||||
url_overview: '/manufacturerpart',
|
||||
url_detail: '/purchasing/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: {
|
||||
label: t`Part Category`,
|
||||
@ -61,7 +65,8 @@ export const ModelInformationDict: ModelDict = {
|
||||
url_overview: '/part/category',
|
||||
url_detail: '/part/category/:pk/',
|
||||
cui_detail: '/part/category/:pk/',
|
||||
api_endpoint: ApiEndpoints.category_list
|
||||
api_endpoint: ApiEndpoints.category_list,
|
||||
admin_url: '/part/partcategory/'
|
||||
},
|
||||
stockitem: {
|
||||
label: t`Stock Item`,
|
||||
@ -69,7 +74,8 @@ export const ModelInformationDict: ModelDict = {
|
||||
url_overview: '/stock/item',
|
||||
url_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: {
|
||||
label: t`Stock Location`,
|
||||
@ -77,7 +83,8 @@ export const ModelInformationDict: ModelDict = {
|
||||
url_overview: '/stock/location',
|
||||
url_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: {
|
||||
label: t`Stock Location Type`,
|
||||
@ -95,7 +102,8 @@ export const ModelInformationDict: ModelDict = {
|
||||
url_overview: '/build',
|
||||
url_detail: '/build/:pk/',
|
||||
cui_detail: '/build/:pk/',
|
||||
api_endpoint: ApiEndpoints.build_order_list
|
||||
api_endpoint: ApiEndpoints.build_order_list,
|
||||
admin_url: '/build/build/'
|
||||
},
|
||||
buildline: {
|
||||
label: t`Build Line`,
|
||||
@ -111,7 +119,8 @@ export const ModelInformationDict: ModelDict = {
|
||||
url_overview: '/company',
|
||||
url_detail: '/company/:pk/',
|
||||
cui_detail: '/company/:pk/',
|
||||
api_endpoint: ApiEndpoints.company_list
|
||||
api_endpoint: ApiEndpoints.company_list,
|
||||
admin_url: '/company/company/'
|
||||
},
|
||||
projectcode: {
|
||||
label: t`Project Code`,
|
||||
@ -126,7 +135,8 @@ export const ModelInformationDict: ModelDict = {
|
||||
url_overview: '/purchasing/purchase-order',
|
||||
url_detail: '/purchasing/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: {
|
||||
label: t`Purchase Order Line`,
|
||||
@ -139,7 +149,8 @@ export const ModelInformationDict: ModelDict = {
|
||||
url_overview: '/sales/sales-order',
|
||||
url_detail: '/sales/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: {
|
||||
label: t`Sales Order Shipment`,
|
||||
@ -154,7 +165,8 @@ export const ModelInformationDict: ModelDict = {
|
||||
url_overview: '/sales/return-order',
|
||||
url_detail: '/sales/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: {
|
||||
label: t`Address`,
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
import { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import AdminButton from '../../components/buttons/AdminButton';
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
@ -347,8 +348,8 @@ export default function BuildDetail() {
|
||||
});
|
||||
|
||||
const buildActions = useMemo(() => {
|
||||
// TODO: Disable certain actions based on user permissions
|
||||
return [
|
||||
<AdminButton model={ModelType.build} pk={build.pk} />,
|
||||
<ActionDropdown
|
||||
key="barcode"
|
||||
tooltip={t`Barcode Actions`}
|
||||
@ -386,7 +387,8 @@ export default function BuildDetail() {
|
||||
}),
|
||||
CancelItemAction({
|
||||
tooltip: t`Cancel order`,
|
||||
onClick: () => cancelBuild.open()
|
||||
onClick: () => cancelBuild.open(),
|
||||
hidden: !user.hasChangeRole(UserRoles.build)
|
||||
// TODO: Hide if build cannot be cancelled
|
||||
}),
|
||||
DuplicateItemAction({
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import AdminButton from '../../components/buttons/AdminButton';
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import DetailsBadge from '../../components/details/DetailsBadge';
|
||||
import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
@ -32,6 +33,7 @@ import { PageDetail } from '../../components/nav/PageDetail';
|
||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { companyFields } from '../../forms/CompanyForms';
|
||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
||||
@ -285,6 +287,7 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
|
||||
|
||||
const companyActions = useMemo(() => {
|
||||
return [
|
||||
<AdminButton model={ModelType.company} pk={company.pk} />,
|
||||
<ActionDropdown
|
||||
key="company"
|
||||
tooltip={t`Company Actions`}
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
import { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import AdminButton from '../../components/buttons/AdminButton';
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
@ -204,6 +205,10 @@ export default function ManufacturerPartDetail() {
|
||||
|
||||
const manufacturerPartActions = useMemo(() => {
|
||||
return [
|
||||
<AdminButton
|
||||
model={ModelType.manufacturerpart}
|
||||
pk={manufacturerPart.pk}
|
||||
/>,
|
||||
<ActionDropdown
|
||||
key="part"
|
||||
tooltip={t`Manufacturer Part Actions`}
|
||||
@ -223,7 +228,7 @@ export default function ManufacturerPartDetail() {
|
||||
]}
|
||||
/>
|
||||
];
|
||||
}, [user]);
|
||||
}, [user, manufacturerPart]);
|
||||
|
||||
const breadcrumbs = useMemo(() => {
|
||||
return [
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import AdminButton from '../../components/buttons/AdminButton';
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import DetailsBadge from '../../components/details/DetailsBadge';
|
||||
import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
@ -243,6 +244,7 @@ export default function SupplierPartDetail() {
|
||||
|
||||
const supplierPartActions = useMemo(() => {
|
||||
return [
|
||||
<AdminButton model={ModelType.supplierpart} pk={supplierPart.pk} />,
|
||||
<ActionDropdown
|
||||
key="part"
|
||||
tooltip={t`Supplier Part Actions`}
|
||||
@ -262,7 +264,7 @@ export default function SupplierPartDetail() {
|
||||
]}
|
||||
/>
|
||||
];
|
||||
}, [user]);
|
||||
}, [user, supplierPart]);
|
||||
|
||||
const supplierPartFields = useSupplierPartFields();
|
||||
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import AdminButton from '../../components/buttons/AdminButton';
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
import {
|
||||
@ -201,6 +202,7 @@ export default function CategoryDetail({}: {}) {
|
||||
|
||||
const categoryActions = useMemo(() => {
|
||||
return [
|
||||
<AdminButton model={ModelType.partcategory} pk={category.pk} />,
|
||||
<ActionDropdown
|
||||
key="category"
|
||||
tooltip={t`Category Actions`}
|
||||
@ -219,7 +221,7 @@ export default function CategoryDetail({}: {}) {
|
||||
]}
|
||||
/>
|
||||
];
|
||||
}, [id, user]);
|
||||
}, [id, user, category.pk]);
|
||||
|
||||
const categoryPanels: PanelType[] = useMemo(
|
||||
() => [
|
||||
|
@ -34,6 +34,7 @@ import { ReactNode, useMemo, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { api } from '../../App';
|
||||
import AdminButton from '../../components/buttons/AdminButton';
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import DetailsBadge from '../../components/details/DetailsBadge';
|
||||
import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
@ -746,6 +747,7 @@ export default function PartDetail() {
|
||||
|
||||
const partActions = useMemo(() => {
|
||||
return [
|
||||
<AdminButton model={ModelType.part} pk={part.pk} />,
|
||||
<BarcodeActionDropdown
|
||||
actions={[
|
||||
ViewBarcodeAction({}),
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import AdminButton from '../../components/buttons/AdminButton';
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
@ -300,6 +301,7 @@ export default function PurchaseOrderDetail() {
|
||||
|
||||
const poActions = useMemo(() => {
|
||||
return [
|
||||
<AdminButton model={ModelType.purchaseorder} pk={order.pk} />,
|
||||
<BarcodeActionDropdown
|
||||
actions={[
|
||||
ViewBarcodeAction({}),
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import AdminButton from '../../components/buttons/AdminButton';
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
@ -285,6 +286,7 @@ export default function ReturnOrderDetail() {
|
||||
|
||||
const orderActions = useMemo(() => {
|
||||
return [
|
||||
<AdminButton model={ModelType.returnorder} pk={order.pk} />,
|
||||
<ActionDropdown
|
||||
key="order-actions"
|
||||
tooltip={t`Order Actions`}
|
||||
@ -306,7 +308,7 @@ export default function ReturnOrderDetail() {
|
||||
]}
|
||||
/>
|
||||
];
|
||||
}, [user]);
|
||||
}, [user, order]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import AdminButton from '../../components/buttons/AdminButton';
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
@ -297,6 +298,7 @@ export default function SalesOrderDetail() {
|
||||
|
||||
const soActions = useMemo(() => {
|
||||
return [
|
||||
<AdminButton model={ModelType.salesorder} pk={order.pk} />,
|
||||
<ActionDropdown
|
||||
key="order-actions"
|
||||
tooltip={t`Order Actions`}
|
||||
@ -316,7 +318,7 @@ export default function SalesOrderDetail() {
|
||||
]}
|
||||
/>
|
||||
];
|
||||
}, [user]);
|
||||
}, [user, order]);
|
||||
|
||||
const orderBadges: ReactNode[] = useMemo(() => {
|
||||
return instanceQuery.isLoading
|
||||
|
@ -10,6 +10,7 @@ import { useMemo, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { ActionButton } from '../../components/buttons/ActionButton';
|
||||
import AdminButton from '../../components/buttons/AdminButton';
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
import {
|
||||
@ -266,6 +267,7 @@ export default function Stock() {
|
||||
|
||||
const locationActions = useMemo(
|
||||
() => [
|
||||
<AdminButton model={ModelType.stocklocation} pk={location.pk} />,
|
||||
<ActionButton
|
||||
icon={<InvenTreeIcon icon="stocktake" />}
|
||||
variant="outline"
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
import { ReactNode, useMemo, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import AdminButton from '../../components/buttons/AdminButton';
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import DetailsBadge from '../../components/details/DetailsBadge';
|
||||
import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
@ -408,6 +409,7 @@ export default function StockDetail() {
|
||||
|
||||
const stockActions = useMemo(
|
||||
() => [
|
||||
<AdminButton model={ModelType.stockitem} pk={stockitem.pk} />,
|
||||
<BarcodeActionDropdown
|
||||
actions={[
|
||||
ViewBarcodeAction({}),
|
||||
|
Loading…
Reference in New Issue
Block a user