mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
[PUI] Implement "build outputs" table (#7115)
* Update build line allocation table - Allow display of "tracked" items in main allocation table * Add resolveItem function for finding nested items * Update BuildLineTable * Allow BuildLineList to be ordered by 'trackable' field * Bump API version * Building out columns * Table tweaks * Fetch list of required test templates * Tweaks * Add placeholders for table actions * Add typing * Add placeholder buttons * Update columns * Add button to duplicate build order * Add placeholder cancel action * Refactoring * Edit company from table * Change "create" to "add" * Change more from Create to Add
This commit is contained in:
parent
5f54aef79a
commit
d30ab932ca
@ -19,9 +19,11 @@ import {
|
||||
SubmitHandler,
|
||||
useForm
|
||||
} from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { api, queryClient } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import {
|
||||
NestedDict,
|
||||
constructField,
|
||||
@ -30,6 +32,7 @@ import {
|
||||
mapFields
|
||||
} from '../../functions/forms';
|
||||
import { invalidResponse } from '../../functions/notifications';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { PathParams } from '../../states/ApiState';
|
||||
import {
|
||||
ApiFormField,
|
||||
@ -59,6 +62,8 @@ export interface ApiFormAction {
|
||||
* @param successMessage : Optional message to display on successful form submission
|
||||
* @param onFormSuccess : A callback function to call when the form is submitted successfully.
|
||||
* @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)
|
||||
*/
|
||||
export interface ApiFormProps {
|
||||
url: ApiEndpoints | string;
|
||||
@ -79,6 +84,8 @@ export interface ApiFormProps {
|
||||
successMessage?: string;
|
||||
onFormSuccess?: (data: any) => void;
|
||||
onFormError?: () => void;
|
||||
modelType?: ModelType;
|
||||
follow?: boolean;
|
||||
actions?: ApiFormAction[];
|
||||
timeout?: number;
|
||||
}
|
||||
@ -183,6 +190,8 @@ export function ApiForm({
|
||||
props: ApiFormProps;
|
||||
optionsLoading: boolean;
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const fields: ApiFormFieldSet = useMemo(() => {
|
||||
return props.fields ?? {};
|
||||
}, [props.fields]);
|
||||
@ -384,6 +393,12 @@ export function ApiForm({
|
||||
props.onFormSuccess(response.data);
|
||||
}
|
||||
|
||||
if (props.follow) {
|
||||
if (props.modelType && response.data?.pk) {
|
||||
navigate(getDetailUrl(props.modelType, response.data?.pk));
|
||||
}
|
||||
}
|
||||
|
||||
// Optionally show a success message
|
||||
if (props.successMessage) {
|
||||
notifications.hide('form-success');
|
||||
|
@ -14,8 +14,10 @@ import {
|
||||
IconTrash,
|
||||
IconUnlink
|
||||
} from '@tabler/icons-react';
|
||||
import { color } from '@uiw/react-codemirror';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
|
||||
import { InvenTreeIcon } from '../../functions/icons';
|
||||
import { notYetImplemented } from '../../functions/notifications';
|
||||
|
||||
export type ActionDropdownItem = {
|
||||
@ -203,6 +205,24 @@ export function DeleteItemAction({
|
||||
};
|
||||
}
|
||||
|
||||
export function CancelItemAction({
|
||||
hidden = false,
|
||||
tooltip,
|
||||
onClick
|
||||
}: {
|
||||
hidden?: boolean;
|
||||
tooltip?: string;
|
||||
onClick?: () => void;
|
||||
}): ActionDropdownItem {
|
||||
return {
|
||||
icon: <InvenTreeIcon icon="cancel" iconProps={{ color: 'red' }} />,
|
||||
name: t`Cancel`,
|
||||
tooltip: tooltip ?? t`Cancel`,
|
||||
onClick: onClick,
|
||||
hidden: hidden
|
||||
};
|
||||
}
|
||||
|
||||
// Common action button for duplicating an item
|
||||
export function DuplicateItemAction({
|
||||
hidden = false,
|
||||
|
@ -268,8 +268,6 @@ function StockOperationsRow({
|
||||
}) {
|
||||
const item = input.item;
|
||||
|
||||
console.log('rec', record);
|
||||
|
||||
const [value, setValue] = useState<StockItemQuantity>(
|
||||
add ? 0 : item.quantity ?? 0
|
||||
);
|
||||
|
@ -12,8 +12,10 @@ import {
|
||||
IconCalendarStats,
|
||||
IconCategory,
|
||||
IconCheck,
|
||||
IconCircleCheck,
|
||||
IconCircleMinus,
|
||||
IconCirclePlus,
|
||||
IconCircleX,
|
||||
IconClipboardList,
|
||||
IconClipboardText,
|
||||
IconCopy,
|
||||
@ -59,6 +61,7 @@ import {
|
||||
IconTool,
|
||||
IconTools,
|
||||
IconTransfer,
|
||||
IconTransitionRight,
|
||||
IconTrash,
|
||||
IconTruck,
|
||||
IconTruckDelivery,
|
||||
@ -130,6 +133,10 @@ const icons = {
|
||||
delete: IconTrash,
|
||||
packaging: IconPackage,
|
||||
packages: IconPackages,
|
||||
install: IconTransitionRight,
|
||||
plus: IconCirclePlus,
|
||||
minus: IconCircleMinus,
|
||||
cancel: IconCircleX,
|
||||
|
||||
// Part Icons
|
||||
active: IconCheck,
|
||||
@ -186,7 +193,8 @@ const icons = {
|
||||
batch_code: IconClipboardText,
|
||||
destination: IconFlag,
|
||||
repeat_destination: IconFlagShare,
|
||||
unlink: IconUnlink
|
||||
unlink: IconUnlink,
|
||||
success: IconCircleCheck
|
||||
};
|
||||
|
||||
export type InvenTreeIconType = keyof typeof icons;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { randomId, useLocalStorage } from '@mantine/hooks';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { TableFilter } from '../tables/Filter';
|
||||
|
||||
@ -22,6 +22,7 @@ export type TableState = {
|
||||
expandedRecords: any[];
|
||||
setExpandedRecords: (records: any[]) => void;
|
||||
selectedRecords: any[];
|
||||
hasSelectedRecords: boolean;
|
||||
setSelectedRecords: (records: any[]) => void;
|
||||
clearSelectedRecords: () => void;
|
||||
hiddenColumns: string[];
|
||||
@ -78,6 +79,11 @@ export function useTable(tableName: string): TableState {
|
||||
setSelectedRecords([]);
|
||||
}, []);
|
||||
|
||||
const hasSelectedRecords = useMemo(
|
||||
() => selectedRecords.length > 0,
|
||||
[selectedRecords]
|
||||
);
|
||||
|
||||
// Total record count
|
||||
const [recordCount, setRecordCount] = useState<number>(0);
|
||||
|
||||
@ -126,6 +132,7 @@ export function useTable(tableName: string): TableState {
|
||||
selectedRecords,
|
||||
setSelectedRecords,
|
||||
clearSelectedRecords,
|
||||
hasSelectedRecords,
|
||||
hiddenColumns,
|
||||
setHiddenColumns,
|
||||
searchTerm,
|
||||
|
@ -22,6 +22,7 @@ import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
import {
|
||||
ActionDropdown,
|
||||
CancelItemAction,
|
||||
DuplicateItemAction,
|
||||
EditItemAction,
|
||||
LinkBarcodeAction,
|
||||
@ -36,12 +37,16 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { useBuildOrderFields } from '../../forms/BuildForms';
|
||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import BuildLineTable from '../../tables/build/BuildLineTable';
|
||||
import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
|
||||
import BuildOutputTable from '../../tables/build/BuildOutputTable';
|
||||
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
||||
import { StockItemTable } from '../../tables/stock/StockItemTable';
|
||||
|
||||
@ -213,7 +218,12 @@ export default function BuildDetail() {
|
||||
{
|
||||
name: 'incomplete-outputs',
|
||||
label: t`Incomplete Outputs`,
|
||||
icon: <IconClipboardList />
|
||||
icon: <IconClipboardList />,
|
||||
content: build.pk ? (
|
||||
<BuildOutputTable buildId={build.pk} partId={build.part} />
|
||||
) : (
|
||||
<Skeleton />
|
||||
)
|
||||
// TODO: Hide if build is complete
|
||||
},
|
||||
{
|
||||
@ -290,6 +300,18 @@ export default function BuildDetail() {
|
||||
}
|
||||
});
|
||||
|
||||
const duplicateBuild = useCreateApiFormModal({
|
||||
url: ApiEndpoints.build_order_list,
|
||||
title: t`Add Build Order`,
|
||||
fields: buildOrderFields,
|
||||
initialData: {
|
||||
...build,
|
||||
reference: undefined
|
||||
},
|
||||
follow: true,
|
||||
modelType: ModelType.build
|
||||
});
|
||||
|
||||
const buildActions = useMemo(() => {
|
||||
// TODO: Disable certain actions based on user permissions
|
||||
return [
|
||||
@ -328,7 +350,13 @@ export default function BuildDetail() {
|
||||
onClick: () => editBuild.open(),
|
||||
hidden: !user.hasChangeRole(UserRoles.build)
|
||||
}),
|
||||
DuplicateItemAction({})
|
||||
CancelItemAction({
|
||||
tooltip: t`Cancel order`
|
||||
}),
|
||||
DuplicateItemAction({
|
||||
onClick: () => duplicateBuild.open(),
|
||||
hidden: !user.hasAddRole(UserRoles.build)
|
||||
})
|
||||
]}
|
||||
/>
|
||||
];
|
||||
@ -349,6 +377,7 @@ export default function BuildDetail() {
|
||||
return (
|
||||
<>
|
||||
{editBuild.modal}
|
||||
{duplicateBuild.modal}
|
||||
<Stack spacing="xs">
|
||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||
<PageDetail
|
||||
|
@ -26,7 +26,10 @@ import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { useManufacturerPartFields } from '../../forms/CompanyForms';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
@ -189,6 +192,17 @@ export default function ManufacturerPartDetail() {
|
||||
onFormSuccess: refreshInstance
|
||||
});
|
||||
|
||||
const duplicateManufacturerPart = useCreateApiFormModal({
|
||||
url: ApiEndpoints.manufacturer_part_list,
|
||||
title: t`Add Manufacturer Part`,
|
||||
fields: editManufacturerPartFields,
|
||||
initialData: {
|
||||
...manufacturerPart
|
||||
},
|
||||
follow: true,
|
||||
modelType: ModelType.manufacturerpart
|
||||
});
|
||||
|
||||
const manufacturerPartActions = useMemo(() => {
|
||||
return [
|
||||
<ActionDropdown
|
||||
@ -197,7 +211,8 @@ export default function ManufacturerPartDetail() {
|
||||
icon={<IconDots />}
|
||||
actions={[
|
||||
DuplicateItemAction({
|
||||
hidden: !user.hasAddRole(UserRoles.purchase_order)
|
||||
hidden: !user.hasAddRole(UserRoles.purchase_order),
|
||||
onClick: () => duplicateManufacturerPart.open()
|
||||
}),
|
||||
EditItemAction({
|
||||
hidden: !user.hasChangeRole(UserRoles.purchase_order),
|
||||
@ -227,6 +242,7 @@ export default function ManufacturerPartDetail() {
|
||||
return (
|
||||
<>
|
||||
{editManufacturerPart.modal}
|
||||
{duplicateManufacturerPart.modal}
|
||||
<Stack spacing="xs">
|
||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||
<PageDetail
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
IconShoppingCart
|
||||
} from '@tabler/icons-react';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import DetailsBadge from '../../components/details/DetailsBadge';
|
||||
@ -26,7 +26,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { useSupplierPartFields } from '../../forms/CompanyForms';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useEditApiFormModal
|
||||
@ -43,8 +42,6 @@ export default function SupplierPartDetail() {
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {
|
||||
instance: supplierPart,
|
||||
instanceQuery,
|
||||
@ -284,11 +281,8 @@ export default function SupplierPartDetail() {
|
||||
initialData: {
|
||||
...supplierPart
|
||||
},
|
||||
onFormSuccess: (response: any) => {
|
||||
if (response.pk) {
|
||||
navigate(getDetailUrl(ModelType.supplierpart, response.pk));
|
||||
}
|
||||
}
|
||||
follow: true,
|
||||
modelType: ModelType.supplierpart
|
||||
});
|
||||
|
||||
const breadcrumbs = useMemo(() => {
|
||||
|
@ -24,7 +24,7 @@ import {
|
||||
} from '@tabler/icons-react';
|
||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { ReactNode, useMemo, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { api } from '../../App';
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
@ -86,7 +86,6 @@ export default function PartDetail() {
|
||||
const { id } = useParams();
|
||||
|
||||
const user = useUserState();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [treeOpen, setTreeOpen] = useState(false);
|
||||
|
||||
@ -680,11 +679,8 @@ export default function PartDetail() {
|
||||
initialData: {
|
||||
...part
|
||||
},
|
||||
onFormSuccess: (response: any) => {
|
||||
if (response.pk) {
|
||||
navigate(getDetailUrl(ModelType.part, response.pk));
|
||||
}
|
||||
}
|
||||
follow: true,
|
||||
modelType: ModelType.part
|
||||
});
|
||||
|
||||
const stockActionProps: StockOperationProps = useMemo(() => {
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
IconPaperclip
|
||||
} from '@tabler/icons-react';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
@ -17,7 +17,8 @@ import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
import {
|
||||
ActionDropdown,
|
||||
BarcodeActionDropdown,
|
||||
DeleteItemAction,
|
||||
CancelItemAction,
|
||||
DuplicateItemAction,
|
||||
EditItemAction,
|
||||
LinkBarcodeAction,
|
||||
UnlinkBarcodeAction,
|
||||
@ -31,7 +32,11 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
|
||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
@ -46,6 +51,7 @@ export default function PurchaseOrderDetail() {
|
||||
const { id } = useParams();
|
||||
|
||||
const user = useUserState();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {
|
||||
instance: order,
|
||||
@ -72,6 +78,18 @@ export default function PurchaseOrderDetail() {
|
||||
}
|
||||
});
|
||||
|
||||
const duplicatePurchaseOrder = useCreateApiFormModal({
|
||||
url: ApiEndpoints.purchase_order_list,
|
||||
title: t`Add Purchase Order`,
|
||||
fields: purchaseOrderFields,
|
||||
initialData: {
|
||||
...order,
|
||||
reference: undefined
|
||||
},
|
||||
follow: true,
|
||||
modelType: ModelType.purchaseorder
|
||||
});
|
||||
|
||||
const detailsPanel = useMemo(() => {
|
||||
if (instanceQuery.isFetching) {
|
||||
return <Skeleton />;
|
||||
@ -299,8 +317,12 @@ export default function PurchaseOrderDetail() {
|
||||
editPurchaseOrder.open();
|
||||
}
|
||||
}),
|
||||
DeleteItemAction({
|
||||
hidden: !user.hasDeleteRole(UserRoles.purchase_order)
|
||||
CancelItemAction({
|
||||
tooltip: t`Cancel order`
|
||||
}),
|
||||
DuplicateItemAction({
|
||||
hidden: !user.hasAddRole(UserRoles.purchase_order),
|
||||
onClick: () => duplicatePurchaseOrder.open()
|
||||
})
|
||||
]}
|
||||
/>
|
||||
@ -322,6 +344,7 @@ export default function PurchaseOrderDetail() {
|
||||
return (
|
||||
<>
|
||||
{editPurchaseOrder.modal}
|
||||
{duplicatePurchaseOrder.modal}
|
||||
<Stack spacing="xs">
|
||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||
<PageDetail
|
||||
|
@ -8,14 +8,16 @@ import {
|
||||
IconPaperclip
|
||||
} from '@tabler/icons-react';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
import {
|
||||
ActionDropdown,
|
||||
CancelItemAction,
|
||||
DeleteItemAction,
|
||||
DuplicateItemAction,
|
||||
EditItemAction
|
||||
} from '../../components/items/ActionDropdown';
|
||||
import { PageDetail } from '../../components/nav/PageDetail';
|
||||
@ -26,7 +28,10 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { useReturnOrderFields } from '../../forms/SalesOrderForms';
|
||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
@ -39,6 +44,7 @@ export default function ReturnOrderDetail() {
|
||||
const { id } = useParams();
|
||||
|
||||
const user = useUserState();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {
|
||||
instance: order,
|
||||
@ -260,6 +266,18 @@ export default function ReturnOrderDetail() {
|
||||
}
|
||||
});
|
||||
|
||||
const duplicateReturnOrder = useCreateApiFormModal({
|
||||
url: ApiEndpoints.return_order_list,
|
||||
title: t`Add Return Order`,
|
||||
fields: returnOrderFields,
|
||||
initialData: {
|
||||
...order,
|
||||
reference: undefined
|
||||
},
|
||||
modelType: ModelType.returnorder,
|
||||
follow: true
|
||||
});
|
||||
|
||||
const orderActions = useMemo(() => {
|
||||
return [
|
||||
<ActionDropdown
|
||||
@ -273,9 +291,12 @@ export default function ReturnOrderDetail() {
|
||||
editReturnOrder.open();
|
||||
}
|
||||
}),
|
||||
DeleteItemAction({
|
||||
hidden: !user.hasDeleteRole(UserRoles.return_order)
|
||||
// TODO: Delete?
|
||||
CancelItemAction({
|
||||
tooltip: t`Cancel order`
|
||||
}),
|
||||
DuplicateItemAction({
|
||||
hidden: !user.hasChangeRole(UserRoles.return_order),
|
||||
onClick: () => duplicateReturnOrder.open()
|
||||
})
|
||||
]}
|
||||
/>
|
||||
@ -285,6 +306,7 @@ export default function ReturnOrderDetail() {
|
||||
return (
|
||||
<>
|
||||
{editReturnOrder.modal}
|
||||
{duplicateReturnOrder.modal}
|
||||
<Stack spacing="xs">
|
||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||
<PageDetail
|
||||
|
@ -11,14 +11,16 @@ import {
|
||||
IconTruckLoading
|
||||
} from '@tabler/icons-react';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
import {
|
||||
ActionDropdown,
|
||||
CancelItemAction,
|
||||
DeleteItemAction,
|
||||
DuplicateItemAction,
|
||||
EditItemAction
|
||||
} from '../../components/items/ActionDropdown';
|
||||
import { PageDetail } from '../../components/nav/PageDetail';
|
||||
@ -29,7 +31,11 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
|
||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
@ -43,6 +49,7 @@ export default function SalesOrderDetail() {
|
||||
const { id } = useParams();
|
||||
|
||||
const user = useUserState();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {
|
||||
instance: order,
|
||||
@ -212,6 +219,18 @@ export default function SalesOrderDetail() {
|
||||
}
|
||||
});
|
||||
|
||||
const duplicateSalesOrder = useCreateApiFormModal({
|
||||
url: ApiEndpoints.sales_order_list,
|
||||
title: t`Add Sales Order`,
|
||||
fields: salesOrderFields,
|
||||
initialData: {
|
||||
...order,
|
||||
reference: undefined
|
||||
},
|
||||
follow: true,
|
||||
modelType: ModelType.salesorder
|
||||
});
|
||||
|
||||
const orderPanels: PanelType[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@ -281,13 +300,14 @@ export default function SalesOrderDetail() {
|
||||
actions={[
|
||||
EditItemAction({
|
||||
hidden: !user.hasChangeRole(UserRoles.sales_order),
|
||||
onClick: () => {
|
||||
editSalesOrder.open();
|
||||
}
|
||||
onClick: () => editSalesOrder.open()
|
||||
}),
|
||||
DeleteItemAction({
|
||||
hidden: !user.hasDeleteRole(UserRoles.sales_order)
|
||||
// TODO: Delete?
|
||||
CancelItemAction({
|
||||
tooltip: t`Cancel order`
|
||||
}),
|
||||
DuplicateItemAction({
|
||||
hidden: !user.hasAddRole(UserRoles.sales_order),
|
||||
onClick: () => duplicateSalesOrder.open()
|
||||
})
|
||||
]}
|
||||
/>
|
||||
@ -309,6 +329,7 @@ export default function SalesOrderDetail() {
|
||||
return (
|
||||
<>
|
||||
{editSalesOrder.modal}
|
||||
{duplicateSalesOrder.modal}
|
||||
<Stack spacing="xs">
|
||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||
<PageDetail
|
||||
|
@ -1,19 +1,9 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
Grid,
|
||||
Group,
|
||||
LoadingOverlay,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text
|
||||
} from '@mantine/core';
|
||||
import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
|
||||
import {
|
||||
IconBookmark,
|
||||
IconBoxPadding,
|
||||
IconChecklist,
|
||||
IconCopy,
|
||||
IconDots,
|
||||
IconHistory,
|
||||
IconInfoCircle,
|
||||
@ -23,7 +13,7 @@ import {
|
||||
IconSitemap
|
||||
} from '@tabler/icons-react';
|
||||
import { ReactNode, useMemo, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import DetailsBadge from '../../components/details/DetailsBadge';
|
||||
@ -74,8 +64,6 @@ export default function StockDetail() {
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [treeOpen, setTreeOpen] = useState(false);
|
||||
|
||||
const {
|
||||
@ -375,11 +363,8 @@ export default function StockDetail() {
|
||||
initialData: {
|
||||
...stockitem
|
||||
},
|
||||
onFormSuccess: (response: any) => {
|
||||
if (response.pk) {
|
||||
navigate(getDetailUrl(ModelType.stockitem, response.pk));
|
||||
}
|
||||
}
|
||||
follow: true,
|
||||
modelType: ModelType.stockitem
|
||||
});
|
||||
|
||||
const stockActionProps: StockOperationProps = useMemo(() => {
|
||||
@ -479,6 +464,11 @@ export default function StockDetail() {
|
||||
return instanceQuery.isLoading
|
||||
? []
|
||||
: [
|
||||
<DetailsBadge
|
||||
color="yellow"
|
||||
label={t`In Production`}
|
||||
visible={stockitem.is_building}
|
||||
/>,
|
||||
<DetailsBadge
|
||||
color="blue"
|
||||
label={t`Serial Number` + `: ${stockitem.serial}`}
|
||||
|
@ -2,8 +2,7 @@
|
||||
* Common rendering functions for table column data.
|
||||
*/
|
||||
import { t } from '@lingui/macro';
|
||||
import { Anchor } from '@mantine/core';
|
||||
import { access } from 'fs';
|
||||
import { Anchor, Text } from '@mantine/core';
|
||||
|
||||
import { YesNoButton } from '../components/buttons/YesNoButton';
|
||||
import { Thumbnail } from '../components/images/Thumbnail';
|
||||
@ -27,6 +26,34 @@ export function PartColumn(part: any, full_name?: boolean) {
|
||||
);
|
||||
}
|
||||
|
||||
export function LocationColumn({
|
||||
accessor,
|
||||
title,
|
||||
sortable,
|
||||
ordering
|
||||
}: {
|
||||
accessor: string;
|
||||
title?: string;
|
||||
sortable?: boolean;
|
||||
ordering?: string;
|
||||
}): TableColumn {
|
||||
return {
|
||||
accessor: accessor,
|
||||
title: title ?? t`Location`,
|
||||
sortable: sortable ?? true,
|
||||
ordering: ordering ?? 'location',
|
||||
render: (record: any) => {
|
||||
let location = resolveItem(record, accessor);
|
||||
|
||||
if (!location) {
|
||||
return <Text italic>{t`No location set`}</Text>;
|
||||
}
|
||||
|
||||
return <Text>{location.name}</Text>;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function BooleanColumn({
|
||||
accessor,
|
||||
title,
|
||||
|
@ -27,7 +27,7 @@ export function TableHoverCard({
|
||||
}
|
||||
|
||||
return (
|
||||
<HoverCard withinPortal={true}>
|
||||
<HoverCard withinPortal={true} closeDelay={20} openDelay={250}>
|
||||
<HoverCard.Target>
|
||||
<Group spacing="xs" position="apart" noWrap={true}>
|
||||
{value}
|
||||
|
@ -298,7 +298,7 @@ export function BomTable({
|
||||
|
||||
const newBomItem = useCreateApiFormModal({
|
||||
url: ApiEndpoints.bom_list,
|
||||
title: t`Create BOM Item`,
|
||||
title: t`Add BOM Item`,
|
||||
fields: bomItemFields(),
|
||||
initialData: {
|
||||
part: partId
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||
import { PartHoverCard } from '../../components/images/Thumbnail';
|
||||
@ -11,7 +10,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { useBuildOrderFields } from '../../forms/BuildForms';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
@ -130,7 +128,6 @@ export function BuildOrderTable({
|
||||
];
|
||||
}, []);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const user = useUserState();
|
||||
|
||||
const table = useTable('buildorder');
|
||||
@ -146,11 +143,8 @@ export function BuildOrderTable({
|
||||
sales_order: salesOrderId,
|
||||
parent: parentBuildId
|
||||
},
|
||||
onFormSuccess: (data: any) => {
|
||||
if (data.pk) {
|
||||
navigate(getDetailUrl(ModelType.build, data.pk));
|
||||
}
|
||||
}
|
||||
follow: true,
|
||||
modelType: ModelType.build
|
||||
});
|
||||
|
||||
const tableActions = useMemo(() => {
|
||||
|
284
src/frontend/src/tables/build/BuildOutputTable.tsx
Normal file
284
src/frontend/src/tables/build/BuildOutputTable.tsx
Normal file
@ -0,0 +1,284 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Group, Text } from '@mantine/core';
|
||||
import {
|
||||
IconCircleCheck,
|
||||
IconCircleX,
|
||||
IconExclamationCircle
|
||||
} from '@tabler/icons-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { api } from '../../App';
|
||||
import { ActionButton } from '../../components/buttons/ActionButton';
|
||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||
import { ProgressBar } from '../../components/items/ProgressBar';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { InvenTreeIcon } from '../../functions/icons';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { TableColumn } from '../Column';
|
||||
import { LocationColumn, PartColumn } from '../ColumnRenderers';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { RowAction } from '../RowActions';
|
||||
import { TableHoverCard } from '../TableHoverCard';
|
||||
|
||||
type TestResultOverview = {
|
||||
name: string;
|
||||
result: boolean;
|
||||
};
|
||||
|
||||
export default function BuildOutputTable({
|
||||
buildId,
|
||||
partId
|
||||
}: {
|
||||
buildId: number;
|
||||
partId: number;
|
||||
}) {
|
||||
const user = useUserState();
|
||||
const table = useTable('build-outputs');
|
||||
|
||||
// Fetch the test templates associated with the partId
|
||||
const { data: testTemplates } = useQuery({
|
||||
queryKey: ['buildoutputtests', partId],
|
||||
queryFn: async () => {
|
||||
if (!partId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return api
|
||||
.get(apiUrl(ApiEndpoints.part_test_template_list), {
|
||||
params: {
|
||||
part: partId,
|
||||
include_inherited: true,
|
||||
enabled: true,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
.then((response) => response.data)
|
||||
.catch(() => []);
|
||||
}
|
||||
});
|
||||
|
||||
const hasRequiredTests: boolean = useMemo(() => {
|
||||
return (testTemplates?.length ?? 0) > 0;
|
||||
}, [partId, testTemplates]);
|
||||
|
||||
// Format table records
|
||||
const formatRecords = useCallback(
|
||||
(records: any[]): any[] => {
|
||||
records?.forEach((record: any, index: number) => {
|
||||
let results: TestResultOverview[] = [];
|
||||
let passCount: number = 0;
|
||||
|
||||
// Iterate through each
|
||||
testTemplates?.forEach((template: any) => {
|
||||
// Find the "newest" result for this template in the returned data
|
||||
let result = record.tests
|
||||
?.filter((test: any) => test.template == template.pk)
|
||||
.sort((a: any, b: any) => {
|
||||
return a.pk < b.pk ? 1 : -1;
|
||||
})
|
||||
.shift();
|
||||
|
||||
if (template?.required && result?.result) {
|
||||
passCount += 1;
|
||||
}
|
||||
|
||||
results.push({
|
||||
name: template.test_name,
|
||||
result: result?.result ?? false
|
||||
});
|
||||
});
|
||||
|
||||
records[index].passCount = passCount;
|
||||
records[index].results = results;
|
||||
});
|
||||
|
||||
return records;
|
||||
},
|
||||
[partId, testTemplates]
|
||||
);
|
||||
|
||||
const tableActions = useMemo(() => {
|
||||
// TODO: Button to create new build output
|
||||
// TODO: Button to complete output(s)
|
||||
// TODO: Button to cancel output(s)
|
||||
// TODO: Button to scrap output(s)
|
||||
return [
|
||||
<AddItemButton
|
||||
tooltip={t`Add Build Output`}
|
||||
hidden={!user.hasAddRole(UserRoles.build)}
|
||||
/>,
|
||||
<ActionButton
|
||||
tooltip={t`Complete selected outputs`}
|
||||
icon={<InvenTreeIcon icon="success" />}
|
||||
color="green"
|
||||
disabled={!table.hasSelectedRecords}
|
||||
/>,
|
||||
<ActionButton
|
||||
tooltip={t`Scrap selected outputs`}
|
||||
icon={<InvenTreeIcon icon="cancel" />}
|
||||
color="red"
|
||||
disabled={!table.hasSelectedRecords}
|
||||
/>,
|
||||
<ActionButton
|
||||
tooltip={t`Cancel selected outputs`}
|
||||
icon={<InvenTreeIcon icon="delete" />}
|
||||
color="red"
|
||||
disabled={!table.hasSelectedRecords}
|
||||
/>
|
||||
];
|
||||
}, [user, partId, buildId, table.hasSelectedRecords]);
|
||||
|
||||
const rowActions = useCallback(
|
||||
(record: any) => {
|
||||
let actions: RowAction[] = [
|
||||
{
|
||||
title: t`Allocate`,
|
||||
tooltip: t`Allocate stock to build output`,
|
||||
color: 'blue',
|
||||
icon: <InvenTreeIcon icon="plus" />
|
||||
},
|
||||
{
|
||||
title: t`Deallocate`,
|
||||
tooltip: t`Deallocate stock from build output`,
|
||||
color: 'red',
|
||||
icon: <InvenTreeIcon icon="minus" />
|
||||
},
|
||||
{
|
||||
title: t`Complete`,
|
||||
tooltip: t`Complete build output`,
|
||||
color: 'green',
|
||||
icon: <InvenTreeIcon icon="success" />
|
||||
},
|
||||
{
|
||||
title: t`Scrap`,
|
||||
tooltip: t`Scrap build output`,
|
||||
color: 'red',
|
||||
icon: <InvenTreeIcon icon="cancel" />
|
||||
},
|
||||
{
|
||||
title: t`Delete`,
|
||||
tooltip: t`Delete build output`,
|
||||
color: 'red',
|
||||
icon: <InvenTreeIcon icon="delete" />
|
||||
}
|
||||
];
|
||||
|
||||
return actions;
|
||||
},
|
||||
[user, partId, buildId]
|
||||
);
|
||||
|
||||
const tableColumns: TableColumn[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
accessor: 'part',
|
||||
sortable: true,
|
||||
render: (record: any) => PartColumn(record?.part_detail)
|
||||
},
|
||||
{
|
||||
accessor: 'quantity',
|
||||
ordering: 'stock',
|
||||
sortable: true,
|
||||
switchable: false,
|
||||
title: t`Build Output`,
|
||||
render: (record: any) => {
|
||||
let text = record.quantity;
|
||||
|
||||
if (record.serial) {
|
||||
text = `# ${record.serial}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Group position="left" noWrap>
|
||||
<Text>{text}</Text>
|
||||
{record.batch && (
|
||||
<Text italic size="sm">
|
||||
{t`Batch`}: {record.batch}
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
},
|
||||
LocationColumn({
|
||||
accessor: 'location_detail'
|
||||
}),
|
||||
{
|
||||
accessor: 'allocations',
|
||||
sortable: false,
|
||||
switchable: false,
|
||||
title: t`Allocated Items`,
|
||||
render: (record: any) => {
|
||||
// TODO: Implement this!
|
||||
return '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: 'tests',
|
||||
sortable: false,
|
||||
switchable: false,
|
||||
title: t`Required Tests`,
|
||||
hidden: !hasRequiredTests,
|
||||
render: (record: any) => {
|
||||
const extra =
|
||||
record.results?.map((result: TestResultOverview) => {
|
||||
return (
|
||||
result && (
|
||||
<Group position="left" key={result.name} noWrap>
|
||||
{result.result ? (
|
||||
<IconCircleCheck color="green" />
|
||||
) : (
|
||||
<IconCircleX color="red" />
|
||||
)}
|
||||
<Text>{result.name}</Text>
|
||||
</Group>
|
||||
)
|
||||
);
|
||||
}) ?? [];
|
||||
|
||||
return (
|
||||
<TableHoverCard
|
||||
value={
|
||||
<ProgressBar
|
||||
progressLabel
|
||||
value={record.passCount ?? 0}
|
||||
maximum={testTemplates?.length ?? 0}
|
||||
/>
|
||||
}
|
||||
extra={extra}
|
||||
title={t`Test Results`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
}, [buildId, partId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<InvenTreeTable
|
||||
tableState={table}
|
||||
url={apiUrl(ApiEndpoints.stock_item_list)}
|
||||
columns={tableColumns}
|
||||
props={{
|
||||
params: {
|
||||
part_detail: true,
|
||||
tests: true,
|
||||
is_building: true,
|
||||
build: buildId
|
||||
},
|
||||
modelType: ModelType.stockitem,
|
||||
dataFormatter: formatRecords,
|
||||
tableActions: tableActions,
|
||||
rowActions: rowActions,
|
||||
enableSelection: true
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Group, Text } from '@mantine/core';
|
||||
import { access } from 'fs';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||
@ -10,13 +10,17 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { companyFields } from '../../forms/CompanyForms';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { BooleanColumn, DescriptionColumn } from '../ColumnRenderers';
|
||||
import { TableFilter } from '../Filter';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { RowEditAction } from '../RowActions';
|
||||
|
||||
/**
|
||||
* A table which displays a list of company records,
|
||||
@ -68,17 +72,21 @@ export function CompanyTable({
|
||||
|
||||
const newCompany = useCreateApiFormModal({
|
||||
url: ApiEndpoints.company_list,
|
||||
title: t`New Company`,
|
||||
title: t`Add Company`,
|
||||
fields: companyFields(),
|
||||
initialData: params,
|
||||
onFormSuccess: (response) => {
|
||||
if (response.pk) {
|
||||
let base = path ?? 'company';
|
||||
navigate(`/${base}/${response.pk}`);
|
||||
} else {
|
||||
table.refreshTable();
|
||||
}
|
||||
}
|
||||
follow: true,
|
||||
modelType: ModelType.company
|
||||
});
|
||||
|
||||
const [selectedCompany, setSelectedCompany] = useState<number>(0);
|
||||
|
||||
const editCompany = useEditApiFormModal({
|
||||
url: ApiEndpoints.company_list,
|
||||
pk: selectedCompany,
|
||||
title: t`Edit Company`,
|
||||
fields: companyFields(),
|
||||
onFormSuccess: (record: any) => table.updateRecord(record)
|
||||
});
|
||||
|
||||
const tableFilters: TableFilter[] = useMemo(() => {
|
||||
@ -120,9 +128,27 @@ export function CompanyTable({
|
||||
];
|
||||
}, [user]);
|
||||
|
||||
const rowActions = useCallback(
|
||||
(record: any) => {
|
||||
return [
|
||||
RowEditAction({
|
||||
hidden:
|
||||
!user.hasChangeRole(UserRoles.purchase_order) &&
|
||||
!user.hasChangeRole(UserRoles.sales_order),
|
||||
onClick: () => {
|
||||
setSelectedCompany(record.pk);
|
||||
editCompany.open();
|
||||
}
|
||||
})
|
||||
];
|
||||
},
|
||||
[user]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{newCompany.modal}
|
||||
{editCompany.modal}
|
||||
<InvenTreeTable
|
||||
url={apiUrl(ApiEndpoints.company_list)}
|
||||
tableState={table}
|
||||
@ -133,6 +159,7 @@ export function CompanyTable({
|
||||
},
|
||||
tableFilters: tableFilters,
|
||||
tableActions: tableActions,
|
||||
rowActions: rowActions,
|
||||
onRowClick: (row: any) => {
|
||||
if (row.pk) {
|
||||
let base = path ?? 'company';
|
||||
|
@ -491,7 +491,7 @@ export function MachineListTable({
|
||||
}, [machineDrivers, createFormMachineType]);
|
||||
|
||||
const createMachineForm = useCreateApiFormModal({
|
||||
title: t`Create machine`,
|
||||
title: t`Add machine`,
|
||||
url: ApiEndpoints.machine_list,
|
||||
fields: {
|
||||
name: {},
|
||||
|
@ -278,11 +278,8 @@ export function PartListTable({ props }: { props: InvenTreeTableProps }) {
|
||||
initialData: {
|
||||
...(props.params ?? {})
|
||||
},
|
||||
onFormSuccess: (data: any) => {
|
||||
if (data.pk) {
|
||||
navigate(getDetailUrl(ModelType.part, data.pk));
|
||||
}
|
||||
}
|
||||
follow: true,
|
||||
modelType: ModelType.part
|
||||
});
|
||||
|
||||
const tableActions = useMemo(() => {
|
||||
|
@ -60,7 +60,7 @@ export function ManufacturerPartTable({ params }: { params: any }): ReactNode {
|
||||
|
||||
const createManufacturerPart = useCreateApiFormModal({
|
||||
url: ApiEndpoints.manufacturer_part_list,
|
||||
title: t`Create Manufacturer Part`,
|
||||
title: t`Add Manufacturer Part`,
|
||||
fields: useManufacturerPartFields(),
|
||||
onFormSuccess: table.refreshTable,
|
||||
initialData: {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||
import { Thumbnail } from '../../components/images/Thumbnail';
|
||||
@ -9,7 +8,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
@ -43,8 +41,6 @@ export function PurchaseOrderTable({
|
||||
supplierId?: number;
|
||||
supplierPartId?: number;
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const table = useTable('purchase-order');
|
||||
const user = useUserState();
|
||||
|
||||
@ -115,13 +111,8 @@ export function PurchaseOrderTable({
|
||||
initialData: {
|
||||
supplier: supplierId
|
||||
},
|
||||
onFormSuccess: (response) => {
|
||||
if (response.pk) {
|
||||
navigate(getDetailUrl(ModelType.purchaseorder, response.pk));
|
||||
} else {
|
||||
table.refreshTable();
|
||||
}
|
||||
}
|
||||
follow: true,
|
||||
modelType: ModelType.purchaseorder
|
||||
});
|
||||
|
||||
const tableActions = useMemo(() => {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||
import { Thumbnail } from '../../components/images/Thumbnail';
|
||||
@ -9,8 +8,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { useReturnOrderFields } from '../../forms/SalesOrderForms';
|
||||
import { notYetImplemented } from '../../functions/notifications';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
@ -37,7 +34,6 @@ import { InvenTreeTable } from '../InvenTreeTable';
|
||||
export function ReturnOrderTable({ params }: { params?: any }) {
|
||||
const table = useTable('return-orders');
|
||||
const user = useUserState();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const tableFilters: TableFilter[] = useMemo(() => {
|
||||
return [
|
||||
@ -101,13 +97,8 @@ export function ReturnOrderTable({ params }: { params?: any }) {
|
||||
url: ApiEndpoints.return_order_list,
|
||||
title: t`Add Return Order`,
|
||||
fields: returnOrderFields,
|
||||
onFormSuccess: (response) => {
|
||||
if (response.pk) {
|
||||
navigate(getDetailUrl(ModelType.returnorder, response.pk));
|
||||
} else {
|
||||
table.refreshTable();
|
||||
}
|
||||
}
|
||||
follow: true,
|
||||
modelType: ModelType.returnorder
|
||||
});
|
||||
|
||||
const tableActions = useMemo(() => {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||
import { Thumbnail } from '../../components/images/Thumbnail';
|
||||
@ -9,7 +8,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
@ -43,8 +41,6 @@ export function SalesOrderTable({
|
||||
const table = useTable('sales-order');
|
||||
const user = useUserState();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const tableFilters: TableFilter[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@ -70,13 +66,8 @@ export function SalesOrderTable({
|
||||
initialData: {
|
||||
customer: customerId
|
||||
},
|
||||
onFormSuccess: (response) => {
|
||||
if (response.pk) {
|
||||
navigate(getDetailUrl(ModelType.salesorder, response.pk));
|
||||
} else {
|
||||
table.refreshTable();
|
||||
}
|
||||
}
|
||||
follow: true,
|
||||
modelType: ModelType.salesorder
|
||||
});
|
||||
|
||||
const tableActions = useMemo(() => {
|
||||
|
@ -240,7 +240,7 @@ export function TemplateTable({
|
||||
const newTemplate = useCreateApiFormModal({
|
||||
url: apiEndpoint,
|
||||
pathParams: { variant },
|
||||
title: t`Create new` + ' ' + templateTypeTranslation,
|
||||
title: t`Add new` + ' ' + templateTypeTranslation,
|
||||
fields: {
|
||||
name: {},
|
||||
description: {},
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Group, Text } from '@mantine/core';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||
import { ActionDropdown } from '../../components/items/ActionDropdown';
|
||||
@ -22,14 +21,15 @@ import {
|
||||
useTransferStockItem
|
||||
} from '../../forms/StockForms';
|
||||
import { InvenTreeIcon } from '../../functions/icons';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { TableColumn } from '../Column';
|
||||
import {
|
||||
DateColumn,
|
||||
DescriptionColumn,
|
||||
LocationColumn,
|
||||
PartColumn,
|
||||
StatusColumn
|
||||
} from '../ColumnRenderers';
|
||||
@ -55,7 +55,7 @@ function stockItemTableColumns(): TableColumn[] {
|
||||
ordering: 'stock',
|
||||
sortable: true,
|
||||
title: t`Stock`,
|
||||
render: (record) => {
|
||||
render: (record: any) => {
|
||||
// TODO: Push this out into a custom renderer
|
||||
let quantity = record?.quantity ?? 0;
|
||||
let allocated = record?.allocated ?? 0;
|
||||
@ -198,16 +198,14 @@ function stockItemTableColumns(): TableColumn[] {
|
||||
accessor: 'batch',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
accessor: 'location',
|
||||
sortable: true,
|
||||
render: function (record: any) {
|
||||
// TODO: Custom renderer for location
|
||||
// TODO: Note, if not "In stock" we don't want to display the actual location here
|
||||
return record?.location_detail?.pathstring ?? record.location ?? '-';
|
||||
}
|
||||
},
|
||||
// TODO: stocktake column
|
||||
LocationColumn({
|
||||
accessor: 'location_detail'
|
||||
}),
|
||||
DateColumn({
|
||||
accessor: 'stocktake_date',
|
||||
title: t`Stocktake`,
|
||||
sortable: true
|
||||
}),
|
||||
{
|
||||
accessor: 'expiry_date',
|
||||
sortable: true,
|
||||
@ -357,8 +355,6 @@ export function StockItemTable({
|
||||
const table = useTable(tableName);
|
||||
const user = useUserState();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const tableActionParams: StockOperationProps = useMemo(() => {
|
||||
return {
|
||||
items: table.selectedRecords,
|
||||
@ -377,11 +373,8 @@ export function StockItemTable({
|
||||
part: params.part,
|
||||
location: params.location
|
||||
},
|
||||
onFormSuccess: (data: any) => {
|
||||
if (data.pk) {
|
||||
navigate(getDetailUrl(ModelType.stockitem, data.pk));
|
||||
}
|
||||
}
|
||||
follow: true,
|
||||
modelType: ModelType.stockitem
|
||||
});
|
||||
|
||||
const transferStock = useTransferStockItem(tableActionParams);
|
||||
|
Loading…
Reference in New Issue
Block a user