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,
|
SubmitHandler,
|
||||||
useForm
|
useForm
|
||||||
} from 'react-hook-form';
|
} from 'react-hook-form';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { api, queryClient } from '../../App';
|
import { api, queryClient } from '../../App';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import {
|
import {
|
||||||
NestedDict,
|
NestedDict,
|
||||||
constructField,
|
constructField,
|
||||||
@ -30,6 +32,7 @@ import {
|
|||||||
mapFields
|
mapFields
|
||||||
} from '../../functions/forms';
|
} from '../../functions/forms';
|
||||||
import { invalidResponse } from '../../functions/notifications';
|
import { invalidResponse } from '../../functions/notifications';
|
||||||
|
import { getDetailUrl } from '../../functions/urls';
|
||||||
import { PathParams } from '../../states/ApiState';
|
import { PathParams } from '../../states/ApiState';
|
||||||
import {
|
import {
|
||||||
ApiFormField,
|
ApiFormField,
|
||||||
@ -59,6 +62,8 @@ export interface ApiFormAction {
|
|||||||
* @param successMessage : Optional message to display on successful form submission
|
* @param successMessage : Optional message to display on successful form submission
|
||||||
* @param onFormSuccess : A callback function to call when the form is submitted successfully.
|
* @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 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 {
|
export interface ApiFormProps {
|
||||||
url: ApiEndpoints | string;
|
url: ApiEndpoints | string;
|
||||||
@ -79,6 +84,8 @@ export interface ApiFormProps {
|
|||||||
successMessage?: string;
|
successMessage?: string;
|
||||||
onFormSuccess?: (data: any) => void;
|
onFormSuccess?: (data: any) => void;
|
||||||
onFormError?: () => void;
|
onFormError?: () => void;
|
||||||
|
modelType?: ModelType;
|
||||||
|
follow?: boolean;
|
||||||
actions?: ApiFormAction[];
|
actions?: ApiFormAction[];
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
}
|
}
|
||||||
@ -183,6 +190,8 @@ export function ApiForm({
|
|||||||
props: ApiFormProps;
|
props: ApiFormProps;
|
||||||
optionsLoading: boolean;
|
optionsLoading: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const fields: ApiFormFieldSet = useMemo(() => {
|
const fields: ApiFormFieldSet = useMemo(() => {
|
||||||
return props.fields ?? {};
|
return props.fields ?? {};
|
||||||
}, [props.fields]);
|
}, [props.fields]);
|
||||||
@ -384,6 +393,12 @@ export function ApiForm({
|
|||||||
props.onFormSuccess(response.data);
|
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
|
// Optionally show a success message
|
||||||
if (props.successMessage) {
|
if (props.successMessage) {
|
||||||
notifications.hide('form-success');
|
notifications.hide('form-success');
|
||||||
|
@ -14,8 +14,10 @@ import {
|
|||||||
IconTrash,
|
IconTrash,
|
||||||
IconUnlink
|
IconUnlink
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
|
import { color } from '@uiw/react-codemirror';
|
||||||
import { ReactNode, useMemo } from 'react';
|
import { ReactNode, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { InvenTreeIcon } from '../../functions/icons';
|
||||||
import { notYetImplemented } from '../../functions/notifications';
|
import { notYetImplemented } from '../../functions/notifications';
|
||||||
|
|
||||||
export type ActionDropdownItem = {
|
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
|
// Common action button for duplicating an item
|
||||||
export function DuplicateItemAction({
|
export function DuplicateItemAction({
|
||||||
hidden = false,
|
hidden = false,
|
||||||
|
@ -268,8 +268,6 @@ function StockOperationsRow({
|
|||||||
}) {
|
}) {
|
||||||
const item = input.item;
|
const item = input.item;
|
||||||
|
|
||||||
console.log('rec', record);
|
|
||||||
|
|
||||||
const [value, setValue] = useState<StockItemQuantity>(
|
const [value, setValue] = useState<StockItemQuantity>(
|
||||||
add ? 0 : item.quantity ?? 0
|
add ? 0 : item.quantity ?? 0
|
||||||
);
|
);
|
||||||
|
@ -12,8 +12,10 @@ import {
|
|||||||
IconCalendarStats,
|
IconCalendarStats,
|
||||||
IconCategory,
|
IconCategory,
|
||||||
IconCheck,
|
IconCheck,
|
||||||
|
IconCircleCheck,
|
||||||
IconCircleMinus,
|
IconCircleMinus,
|
||||||
IconCirclePlus,
|
IconCirclePlus,
|
||||||
|
IconCircleX,
|
||||||
IconClipboardList,
|
IconClipboardList,
|
||||||
IconClipboardText,
|
IconClipboardText,
|
||||||
IconCopy,
|
IconCopy,
|
||||||
@ -59,6 +61,7 @@ import {
|
|||||||
IconTool,
|
IconTool,
|
||||||
IconTools,
|
IconTools,
|
||||||
IconTransfer,
|
IconTransfer,
|
||||||
|
IconTransitionRight,
|
||||||
IconTrash,
|
IconTrash,
|
||||||
IconTruck,
|
IconTruck,
|
||||||
IconTruckDelivery,
|
IconTruckDelivery,
|
||||||
@ -130,6 +133,10 @@ const icons = {
|
|||||||
delete: IconTrash,
|
delete: IconTrash,
|
||||||
packaging: IconPackage,
|
packaging: IconPackage,
|
||||||
packages: IconPackages,
|
packages: IconPackages,
|
||||||
|
install: IconTransitionRight,
|
||||||
|
plus: IconCirclePlus,
|
||||||
|
minus: IconCircleMinus,
|
||||||
|
cancel: IconCircleX,
|
||||||
|
|
||||||
// Part Icons
|
// Part Icons
|
||||||
active: IconCheck,
|
active: IconCheck,
|
||||||
@ -186,7 +193,8 @@ const icons = {
|
|||||||
batch_code: IconClipboardText,
|
batch_code: IconClipboardText,
|
||||||
destination: IconFlag,
|
destination: IconFlag,
|
||||||
repeat_destination: IconFlagShare,
|
repeat_destination: IconFlagShare,
|
||||||
unlink: IconUnlink
|
unlink: IconUnlink,
|
||||||
|
success: IconCircleCheck
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InvenTreeIconType = keyof typeof icons;
|
export type InvenTreeIconType = keyof typeof icons;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { randomId, useLocalStorage } from '@mantine/hooks';
|
import { randomId, useLocalStorage } from '@mantine/hooks';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { TableFilter } from '../tables/Filter';
|
import { TableFilter } from '../tables/Filter';
|
||||||
|
|
||||||
@ -22,6 +22,7 @@ export type TableState = {
|
|||||||
expandedRecords: any[];
|
expandedRecords: any[];
|
||||||
setExpandedRecords: (records: any[]) => void;
|
setExpandedRecords: (records: any[]) => void;
|
||||||
selectedRecords: any[];
|
selectedRecords: any[];
|
||||||
|
hasSelectedRecords: boolean;
|
||||||
setSelectedRecords: (records: any[]) => void;
|
setSelectedRecords: (records: any[]) => void;
|
||||||
clearSelectedRecords: () => void;
|
clearSelectedRecords: () => void;
|
||||||
hiddenColumns: string[];
|
hiddenColumns: string[];
|
||||||
@ -78,6 +79,11 @@ export function useTable(tableName: string): TableState {
|
|||||||
setSelectedRecords([]);
|
setSelectedRecords([]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const hasSelectedRecords = useMemo(
|
||||||
|
() => selectedRecords.length > 0,
|
||||||
|
[selectedRecords]
|
||||||
|
);
|
||||||
|
|
||||||
// Total record count
|
// Total record count
|
||||||
const [recordCount, setRecordCount] = useState<number>(0);
|
const [recordCount, setRecordCount] = useState<number>(0);
|
||||||
|
|
||||||
@ -126,6 +132,7 @@ export function useTable(tableName: string): TableState {
|
|||||||
selectedRecords,
|
selectedRecords,
|
||||||
setSelectedRecords,
|
setSelectedRecords,
|
||||||
clearSelectedRecords,
|
clearSelectedRecords,
|
||||||
|
hasSelectedRecords,
|
||||||
hiddenColumns,
|
hiddenColumns,
|
||||||
setHiddenColumns,
|
setHiddenColumns,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
|
@ -22,6 +22,7 @@ import { DetailsImage } from '../../components/details/DetailsImage';
|
|||||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||||
import {
|
import {
|
||||||
ActionDropdown,
|
ActionDropdown,
|
||||||
|
CancelItemAction,
|
||||||
DuplicateItemAction,
|
DuplicateItemAction,
|
||||||
EditItemAction,
|
EditItemAction,
|
||||||
LinkBarcodeAction,
|
LinkBarcodeAction,
|
||||||
@ -36,12 +37,16 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { useBuildOrderFields } from '../../forms/BuildForms';
|
import { useBuildOrderFields } from '../../forms/BuildForms';
|
||||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
import {
|
||||||
|
useCreateApiFormModal,
|
||||||
|
useEditApiFormModal
|
||||||
|
} from '../../hooks/UseForm';
|
||||||
import { useInstance } from '../../hooks/UseInstance';
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import BuildLineTable from '../../tables/build/BuildLineTable';
|
import BuildLineTable from '../../tables/build/BuildLineTable';
|
||||||
import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
|
import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
|
||||||
|
import BuildOutputTable from '../../tables/build/BuildOutputTable';
|
||||||
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
||||||
import { StockItemTable } from '../../tables/stock/StockItemTable';
|
import { StockItemTable } from '../../tables/stock/StockItemTable';
|
||||||
|
|
||||||
@ -213,7 +218,12 @@ export default function BuildDetail() {
|
|||||||
{
|
{
|
||||||
name: 'incomplete-outputs',
|
name: 'incomplete-outputs',
|
||||||
label: t`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
|
// 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(() => {
|
const buildActions = useMemo(() => {
|
||||||
// TODO: Disable certain actions based on user permissions
|
// TODO: Disable certain actions based on user permissions
|
||||||
return [
|
return [
|
||||||
@ -328,7 +350,13 @@ export default function BuildDetail() {
|
|||||||
onClick: () => editBuild.open(),
|
onClick: () => editBuild.open(),
|
||||||
hidden: !user.hasChangeRole(UserRoles.build)
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{editBuild.modal}
|
{editBuild.modal}
|
||||||
|
{duplicateBuild.modal}
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<PageDetail
|
<PageDetail
|
||||||
|
@ -26,7 +26,10 @@ import { ModelType } from '../../enums/ModelType';
|
|||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { useManufacturerPartFields } from '../../forms/CompanyForms';
|
import { useManufacturerPartFields } from '../../forms/CompanyForms';
|
||||||
import { getDetailUrl } from '../../functions/urls';
|
import { getDetailUrl } from '../../functions/urls';
|
||||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
import {
|
||||||
|
useCreateApiFormModal,
|
||||||
|
useEditApiFormModal
|
||||||
|
} from '../../hooks/UseForm';
|
||||||
import { useInstance } from '../../hooks/UseInstance';
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
@ -189,6 +192,17 @@ export default function ManufacturerPartDetail() {
|
|||||||
onFormSuccess: refreshInstance
|
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(() => {
|
const manufacturerPartActions = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
@ -197,7 +211,8 @@ export default function ManufacturerPartDetail() {
|
|||||||
icon={<IconDots />}
|
icon={<IconDots />}
|
||||||
actions={[
|
actions={[
|
||||||
DuplicateItemAction({
|
DuplicateItemAction({
|
||||||
hidden: !user.hasAddRole(UserRoles.purchase_order)
|
hidden: !user.hasAddRole(UserRoles.purchase_order),
|
||||||
|
onClick: () => duplicateManufacturerPart.open()
|
||||||
}),
|
}),
|
||||||
EditItemAction({
|
EditItemAction({
|
||||||
hidden: !user.hasChangeRole(UserRoles.purchase_order),
|
hidden: !user.hasChangeRole(UserRoles.purchase_order),
|
||||||
@ -227,6 +242,7 @@ export default function ManufacturerPartDetail() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{editManufacturerPart.modal}
|
{editManufacturerPart.modal}
|
||||||
|
{duplicateManufacturerPart.modal}
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<PageDetail
|
<PageDetail
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
IconShoppingCart
|
IconShoppingCart
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { ReactNode, useMemo } from '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 { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||||
import DetailsBadge from '../../components/details/DetailsBadge';
|
import DetailsBadge from '../../components/details/DetailsBadge';
|
||||||
@ -26,7 +26,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { useSupplierPartFields } from '../../forms/CompanyForms';
|
import { useSupplierPartFields } from '../../forms/CompanyForms';
|
||||||
import { getDetailUrl } from '../../functions/urls';
|
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
useEditApiFormModal
|
useEditApiFormModal
|
||||||
@ -43,8 +42,6 @@ export default function SupplierPartDetail() {
|
|||||||
|
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
instance: supplierPart,
|
instance: supplierPart,
|
||||||
instanceQuery,
|
instanceQuery,
|
||||||
@ -284,11 +281,8 @@ export default function SupplierPartDetail() {
|
|||||||
initialData: {
|
initialData: {
|
||||||
...supplierPart
|
...supplierPart
|
||||||
},
|
},
|
||||||
onFormSuccess: (response: any) => {
|
follow: true,
|
||||||
if (response.pk) {
|
modelType: ModelType.supplierpart
|
||||||
navigate(getDetailUrl(ModelType.supplierpart, response.pk));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const breadcrumbs = useMemo(() => {
|
const breadcrumbs = useMemo(() => {
|
||||||
|
@ -24,7 +24,7 @@ import {
|
|||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||||
import { ReactNode, useMemo, useState } from 'react';
|
import { ReactNode, useMemo, useState } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { api } from '../../App';
|
import { api } from '../../App';
|
||||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||||
@ -86,7 +86,6 @@ export default function PartDetail() {
|
|||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const [treeOpen, setTreeOpen] = useState(false);
|
const [treeOpen, setTreeOpen] = useState(false);
|
||||||
|
|
||||||
@ -680,11 +679,8 @@ export default function PartDetail() {
|
|||||||
initialData: {
|
initialData: {
|
||||||
...part
|
...part
|
||||||
},
|
},
|
||||||
onFormSuccess: (response: any) => {
|
follow: true,
|
||||||
if (response.pk) {
|
modelType: ModelType.part
|
||||||
navigate(getDetailUrl(ModelType.part, response.pk));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const stockActionProps: StockOperationProps = useMemo(() => {
|
const stockActionProps: StockOperationProps = useMemo(() => {
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
IconPaperclip
|
IconPaperclip
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { ReactNode, useMemo } from '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 { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||||
import { DetailsImage } from '../../components/details/DetailsImage';
|
import { DetailsImage } from '../../components/details/DetailsImage';
|
||||||
@ -17,7 +17,8 @@ import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
|||||||
import {
|
import {
|
||||||
ActionDropdown,
|
ActionDropdown,
|
||||||
BarcodeActionDropdown,
|
BarcodeActionDropdown,
|
||||||
DeleteItemAction,
|
CancelItemAction,
|
||||||
|
DuplicateItemAction,
|
||||||
EditItemAction,
|
EditItemAction,
|
||||||
LinkBarcodeAction,
|
LinkBarcodeAction,
|
||||||
UnlinkBarcodeAction,
|
UnlinkBarcodeAction,
|
||||||
@ -31,7 +32,11 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
|
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 { useInstance } from '../../hooks/UseInstance';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
@ -46,6 +51,7 @@ export default function PurchaseOrderDetail() {
|
|||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
instance: order,
|
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(() => {
|
const detailsPanel = useMemo(() => {
|
||||||
if (instanceQuery.isFetching) {
|
if (instanceQuery.isFetching) {
|
||||||
return <Skeleton />;
|
return <Skeleton />;
|
||||||
@ -299,8 +317,12 @@ export default function PurchaseOrderDetail() {
|
|||||||
editPurchaseOrder.open();
|
editPurchaseOrder.open();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
DeleteItemAction({
|
CancelItemAction({
|
||||||
hidden: !user.hasDeleteRole(UserRoles.purchase_order)
|
tooltip: t`Cancel order`
|
||||||
|
}),
|
||||||
|
DuplicateItemAction({
|
||||||
|
hidden: !user.hasAddRole(UserRoles.purchase_order),
|
||||||
|
onClick: () => duplicatePurchaseOrder.open()
|
||||||
})
|
})
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@ -322,6 +344,7 @@ export default function PurchaseOrderDetail() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{editPurchaseOrder.modal}
|
{editPurchaseOrder.modal}
|
||||||
|
{duplicatePurchaseOrder.modal}
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<PageDetail
|
<PageDetail
|
||||||
|
@ -8,14 +8,16 @@ import {
|
|||||||
IconPaperclip
|
IconPaperclip
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { ReactNode, useMemo } from '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 { 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';
|
||||||
import {
|
import {
|
||||||
ActionDropdown,
|
ActionDropdown,
|
||||||
|
CancelItemAction,
|
||||||
DeleteItemAction,
|
DeleteItemAction,
|
||||||
|
DuplicateItemAction,
|
||||||
EditItemAction
|
EditItemAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
@ -26,7 +28,10 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { useReturnOrderFields } from '../../forms/SalesOrderForms';
|
import { useReturnOrderFields } from '../../forms/SalesOrderForms';
|
||||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
import {
|
||||||
|
useCreateApiFormModal,
|
||||||
|
useEditApiFormModal
|
||||||
|
} from '../../hooks/UseForm';
|
||||||
import { useInstance } from '../../hooks/UseInstance';
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
@ -39,6 +44,7 @@ export default function ReturnOrderDetail() {
|
|||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
instance: order,
|
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(() => {
|
const orderActions = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
@ -273,9 +291,12 @@ export default function ReturnOrderDetail() {
|
|||||||
editReturnOrder.open();
|
editReturnOrder.open();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
DeleteItemAction({
|
CancelItemAction({
|
||||||
hidden: !user.hasDeleteRole(UserRoles.return_order)
|
tooltip: t`Cancel order`
|
||||||
// TODO: Delete?
|
}),
|
||||||
|
DuplicateItemAction({
|
||||||
|
hidden: !user.hasChangeRole(UserRoles.return_order),
|
||||||
|
onClick: () => duplicateReturnOrder.open()
|
||||||
})
|
})
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@ -285,6 +306,7 @@ export default function ReturnOrderDetail() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{editReturnOrder.modal}
|
{editReturnOrder.modal}
|
||||||
|
{duplicateReturnOrder.modal}
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<PageDetail
|
<PageDetail
|
||||||
|
@ -11,14 +11,16 @@ import {
|
|||||||
IconTruckLoading
|
IconTruckLoading
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { ReactNode, useMemo } from '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 { 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';
|
||||||
import {
|
import {
|
||||||
ActionDropdown,
|
ActionDropdown,
|
||||||
|
CancelItemAction,
|
||||||
DeleteItemAction,
|
DeleteItemAction,
|
||||||
|
DuplicateItemAction,
|
||||||
EditItemAction
|
EditItemAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
@ -29,7 +31,11 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
|
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 { useInstance } from '../../hooks/UseInstance';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
@ -43,6 +49,7 @@ export default function SalesOrderDetail() {
|
|||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
instance: order,
|
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(() => {
|
const orderPanels: PanelType[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -281,13 +300,14 @@ export default function SalesOrderDetail() {
|
|||||||
actions={[
|
actions={[
|
||||||
EditItemAction({
|
EditItemAction({
|
||||||
hidden: !user.hasChangeRole(UserRoles.sales_order),
|
hidden: !user.hasChangeRole(UserRoles.sales_order),
|
||||||
onClick: () => {
|
onClick: () => editSalesOrder.open()
|
||||||
editSalesOrder.open();
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
DeleteItemAction({
|
CancelItemAction({
|
||||||
hidden: !user.hasDeleteRole(UserRoles.sales_order)
|
tooltip: t`Cancel order`
|
||||||
// TODO: Delete?
|
}),
|
||||||
|
DuplicateItemAction({
|
||||||
|
hidden: !user.hasAddRole(UserRoles.sales_order),
|
||||||
|
onClick: () => duplicateSalesOrder.open()
|
||||||
})
|
})
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@ -309,6 +329,7 @@ export default function SalesOrderDetail() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{editSalesOrder.modal}
|
{editSalesOrder.modal}
|
||||||
|
{duplicateSalesOrder.modal}
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<PageDetail
|
<PageDetail
|
||||||
|
@ -1,19 +1,9 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import {
|
import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
|
||||||
Alert,
|
|
||||||
Badge,
|
|
||||||
Grid,
|
|
||||||
Group,
|
|
||||||
LoadingOverlay,
|
|
||||||
Skeleton,
|
|
||||||
Stack,
|
|
||||||
Text
|
|
||||||
} from '@mantine/core';
|
|
||||||
import {
|
import {
|
||||||
IconBookmark,
|
IconBookmark,
|
||||||
IconBoxPadding,
|
IconBoxPadding,
|
||||||
IconChecklist,
|
IconChecklist,
|
||||||
IconCopy,
|
|
||||||
IconDots,
|
IconDots,
|
||||||
IconHistory,
|
IconHistory,
|
||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
@ -23,7 +13,7 @@ import {
|
|||||||
IconSitemap
|
IconSitemap
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { ReactNode, useMemo, useState } from '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 { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||||
import DetailsBadge from '../../components/details/DetailsBadge';
|
import DetailsBadge from '../../components/details/DetailsBadge';
|
||||||
@ -74,8 +64,6 @@ export default function StockDetail() {
|
|||||||
|
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const [treeOpen, setTreeOpen] = useState(false);
|
const [treeOpen, setTreeOpen] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -375,11 +363,8 @@ export default function StockDetail() {
|
|||||||
initialData: {
|
initialData: {
|
||||||
...stockitem
|
...stockitem
|
||||||
},
|
},
|
||||||
onFormSuccess: (response: any) => {
|
follow: true,
|
||||||
if (response.pk) {
|
modelType: ModelType.stockitem
|
||||||
navigate(getDetailUrl(ModelType.stockitem, response.pk));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const stockActionProps: StockOperationProps = useMemo(() => {
|
const stockActionProps: StockOperationProps = useMemo(() => {
|
||||||
@ -479,6 +464,11 @@ export default function StockDetail() {
|
|||||||
return instanceQuery.isLoading
|
return instanceQuery.isLoading
|
||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
|
<DetailsBadge
|
||||||
|
color="yellow"
|
||||||
|
label={t`In Production`}
|
||||||
|
visible={stockitem.is_building}
|
||||||
|
/>,
|
||||||
<DetailsBadge
|
<DetailsBadge
|
||||||
color="blue"
|
color="blue"
|
||||||
label={t`Serial Number` + `: ${stockitem.serial}`}
|
label={t`Serial Number` + `: ${stockitem.serial}`}
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
* Common rendering functions for table column data.
|
* Common rendering functions for table column data.
|
||||||
*/
|
*/
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Anchor } from '@mantine/core';
|
import { Anchor, Text } from '@mantine/core';
|
||||||
import { access } from 'fs';
|
|
||||||
|
|
||||||
import { YesNoButton } from '../components/buttons/YesNoButton';
|
import { YesNoButton } from '../components/buttons/YesNoButton';
|
||||||
import { Thumbnail } from '../components/images/Thumbnail';
|
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({
|
export function BooleanColumn({
|
||||||
accessor,
|
accessor,
|
||||||
title,
|
title,
|
||||||
|
@ -27,7 +27,7 @@ export function TableHoverCard({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HoverCard withinPortal={true}>
|
<HoverCard withinPortal={true} closeDelay={20} openDelay={250}>
|
||||||
<HoverCard.Target>
|
<HoverCard.Target>
|
||||||
<Group spacing="xs" position="apart" noWrap={true}>
|
<Group spacing="xs" position="apart" noWrap={true}>
|
||||||
{value}
|
{value}
|
||||||
|
@ -298,7 +298,7 @@ export function BomTable({
|
|||||||
|
|
||||||
const newBomItem = useCreateApiFormModal({
|
const newBomItem = useCreateApiFormModal({
|
||||||
url: ApiEndpoints.bom_list,
|
url: ApiEndpoints.bom_list,
|
||||||
title: t`Create BOM Item`,
|
title: t`Add BOM Item`,
|
||||||
fields: bomItemFields(),
|
fields: bomItemFields(),
|
||||||
initialData: {
|
initialData: {
|
||||||
part: partId
|
part: partId
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
import { PartHoverCard } from '../../components/images/Thumbnail';
|
import { PartHoverCard } from '../../components/images/Thumbnail';
|
||||||
@ -11,7 +10,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { useBuildOrderFields } from '../../forms/BuildForms';
|
import { useBuildOrderFields } from '../../forms/BuildForms';
|
||||||
import { getDetailUrl } from '../../functions/urls';
|
|
||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
@ -130,7 +128,6 @@ export function BuildOrderTable({
|
|||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
|
||||||
const table = useTable('buildorder');
|
const table = useTable('buildorder');
|
||||||
@ -146,11 +143,8 @@ export function BuildOrderTable({
|
|||||||
sales_order: salesOrderId,
|
sales_order: salesOrderId,
|
||||||
parent: parentBuildId
|
parent: parentBuildId
|
||||||
},
|
},
|
||||||
onFormSuccess: (data: any) => {
|
follow: true,
|
||||||
if (data.pk) {
|
modelType: ModelType.build
|
||||||
navigate(getDetailUrl(ModelType.build, data.pk));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const tableActions = useMemo(() => {
|
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 { t } from '@lingui/macro';
|
||||||
import { Group, Text } from '@mantine/core';
|
import { Group, Text } from '@mantine/core';
|
||||||
import { access } from 'fs';
|
import { access } from 'fs';
|
||||||
import { useMemo } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
@ -10,13 +10,17 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
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 { useCreateApiFormModal } from '../../hooks/UseForm';
|
import {
|
||||||
|
useCreateApiFormModal,
|
||||||
|
useEditApiFormModal
|
||||||
|
} from '../../hooks/UseForm';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import { BooleanColumn, DescriptionColumn } from '../ColumnRenderers';
|
import { BooleanColumn, DescriptionColumn } from '../ColumnRenderers';
|
||||||
import { TableFilter } from '../Filter';
|
import { TableFilter } from '../Filter';
|
||||||
import { InvenTreeTable } from '../InvenTreeTable';
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
|
import { RowEditAction } from '../RowActions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A table which displays a list of company records,
|
* A table which displays a list of company records,
|
||||||
@ -68,17 +72,21 @@ export function CompanyTable({
|
|||||||
|
|
||||||
const newCompany = useCreateApiFormModal({
|
const newCompany = useCreateApiFormModal({
|
||||||
url: ApiEndpoints.company_list,
|
url: ApiEndpoints.company_list,
|
||||||
title: t`New Company`,
|
title: t`Add Company`,
|
||||||
fields: companyFields(),
|
fields: companyFields(),
|
||||||
initialData: params,
|
initialData: params,
|
||||||
onFormSuccess: (response) => {
|
follow: true,
|
||||||
if (response.pk) {
|
modelType: ModelType.company
|
||||||
let base = path ?? 'company';
|
});
|
||||||
navigate(`/${base}/${response.pk}`);
|
|
||||||
} else {
|
const [selectedCompany, setSelectedCompany] = useState<number>(0);
|
||||||
table.refreshTable();
|
|
||||||
}
|
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(() => {
|
const tableFilters: TableFilter[] = useMemo(() => {
|
||||||
@ -120,9 +128,27 @@ export function CompanyTable({
|
|||||||
];
|
];
|
||||||
}, [user]);
|
}, [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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{newCompany.modal}
|
{newCompany.modal}
|
||||||
|
{editCompany.modal}
|
||||||
<InvenTreeTable
|
<InvenTreeTable
|
||||||
url={apiUrl(ApiEndpoints.company_list)}
|
url={apiUrl(ApiEndpoints.company_list)}
|
||||||
tableState={table}
|
tableState={table}
|
||||||
@ -133,6 +159,7 @@ export function CompanyTable({
|
|||||||
},
|
},
|
||||||
tableFilters: tableFilters,
|
tableFilters: tableFilters,
|
||||||
tableActions: tableActions,
|
tableActions: tableActions,
|
||||||
|
rowActions: rowActions,
|
||||||
onRowClick: (row: any) => {
|
onRowClick: (row: any) => {
|
||||||
if (row.pk) {
|
if (row.pk) {
|
||||||
let base = path ?? 'company';
|
let base = path ?? 'company';
|
||||||
|
@ -491,7 +491,7 @@ export function MachineListTable({
|
|||||||
}, [machineDrivers, createFormMachineType]);
|
}, [machineDrivers, createFormMachineType]);
|
||||||
|
|
||||||
const createMachineForm = useCreateApiFormModal({
|
const createMachineForm = useCreateApiFormModal({
|
||||||
title: t`Create machine`,
|
title: t`Add machine`,
|
||||||
url: ApiEndpoints.machine_list,
|
url: ApiEndpoints.machine_list,
|
||||||
fields: {
|
fields: {
|
||||||
name: {},
|
name: {},
|
||||||
|
@ -278,11 +278,8 @@ export function PartListTable({ props }: { props: InvenTreeTableProps }) {
|
|||||||
initialData: {
|
initialData: {
|
||||||
...(props.params ?? {})
|
...(props.params ?? {})
|
||||||
},
|
},
|
||||||
onFormSuccess: (data: any) => {
|
follow: true,
|
||||||
if (data.pk) {
|
modelType: ModelType.part
|
||||||
navigate(getDetailUrl(ModelType.part, data.pk));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const tableActions = useMemo(() => {
|
const tableActions = useMemo(() => {
|
||||||
|
@ -60,7 +60,7 @@ export function ManufacturerPartTable({ params }: { params: any }): ReactNode {
|
|||||||
|
|
||||||
const createManufacturerPart = useCreateApiFormModal({
|
const createManufacturerPart = useCreateApiFormModal({
|
||||||
url: ApiEndpoints.manufacturer_part_list,
|
url: ApiEndpoints.manufacturer_part_list,
|
||||||
title: t`Create Manufacturer Part`,
|
title: t`Add Manufacturer Part`,
|
||||||
fields: useManufacturerPartFields(),
|
fields: useManufacturerPartFields(),
|
||||||
onFormSuccess: table.refreshTable,
|
onFormSuccess: table.refreshTable,
|
||||||
initialData: {
|
initialData: {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
import { Thumbnail } from '../../components/images/Thumbnail';
|
import { Thumbnail } from '../../components/images/Thumbnail';
|
||||||
@ -9,7 +8,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
|
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
|
||||||
import { getDetailUrl } from '../../functions/urls';
|
|
||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
@ -43,8 +41,6 @@ export function PurchaseOrderTable({
|
|||||||
supplierId?: number;
|
supplierId?: number;
|
||||||
supplierPartId?: number;
|
supplierPartId?: number;
|
||||||
}) {
|
}) {
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const table = useTable('purchase-order');
|
const table = useTable('purchase-order');
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
|
||||||
@ -115,13 +111,8 @@ export function PurchaseOrderTable({
|
|||||||
initialData: {
|
initialData: {
|
||||||
supplier: supplierId
|
supplier: supplierId
|
||||||
},
|
},
|
||||||
onFormSuccess: (response) => {
|
follow: true,
|
||||||
if (response.pk) {
|
modelType: ModelType.purchaseorder
|
||||||
navigate(getDetailUrl(ModelType.purchaseorder, response.pk));
|
|
||||||
} else {
|
|
||||||
table.refreshTable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const tableActions = useMemo(() => {
|
const tableActions = useMemo(() => {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
import { Thumbnail } from '../../components/images/Thumbnail';
|
import { Thumbnail } from '../../components/images/Thumbnail';
|
||||||
@ -9,8 +8,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { useReturnOrderFields } from '../../forms/SalesOrderForms';
|
import { useReturnOrderFields } from '../../forms/SalesOrderForms';
|
||||||
import { notYetImplemented } from '../../functions/notifications';
|
|
||||||
import { getDetailUrl } from '../../functions/urls';
|
|
||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
@ -37,7 +34,6 @@ import { InvenTreeTable } from '../InvenTreeTable';
|
|||||||
export function ReturnOrderTable({ params }: { params?: any }) {
|
export function ReturnOrderTable({ params }: { params?: any }) {
|
||||||
const table = useTable('return-orders');
|
const table = useTable('return-orders');
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const tableFilters: TableFilter[] = useMemo(() => {
|
const tableFilters: TableFilter[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
@ -101,13 +97,8 @@ export function ReturnOrderTable({ params }: { params?: any }) {
|
|||||||
url: ApiEndpoints.return_order_list,
|
url: ApiEndpoints.return_order_list,
|
||||||
title: t`Add Return Order`,
|
title: t`Add Return Order`,
|
||||||
fields: returnOrderFields,
|
fields: returnOrderFields,
|
||||||
onFormSuccess: (response) => {
|
follow: true,
|
||||||
if (response.pk) {
|
modelType: ModelType.returnorder
|
||||||
navigate(getDetailUrl(ModelType.returnorder, response.pk));
|
|
||||||
} else {
|
|
||||||
table.refreshTable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const tableActions = useMemo(() => {
|
const tableActions = useMemo(() => {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
import { Thumbnail } from '../../components/images/Thumbnail';
|
import { Thumbnail } from '../../components/images/Thumbnail';
|
||||||
@ -9,7 +8,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
|
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
|
||||||
import { getDetailUrl } from '../../functions/urls';
|
|
||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
@ -43,8 +41,6 @@ export function SalesOrderTable({
|
|||||||
const table = useTable('sales-order');
|
const table = useTable('sales-order');
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const tableFilters: TableFilter[] = useMemo(() => {
|
const tableFilters: TableFilter[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -70,13 +66,8 @@ export function SalesOrderTable({
|
|||||||
initialData: {
|
initialData: {
|
||||||
customer: customerId
|
customer: customerId
|
||||||
},
|
},
|
||||||
onFormSuccess: (response) => {
|
follow: true,
|
||||||
if (response.pk) {
|
modelType: ModelType.salesorder
|
||||||
navigate(getDetailUrl(ModelType.salesorder, response.pk));
|
|
||||||
} else {
|
|
||||||
table.refreshTable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const tableActions = useMemo(() => {
|
const tableActions = useMemo(() => {
|
||||||
|
@ -240,7 +240,7 @@ export function TemplateTable({
|
|||||||
const newTemplate = useCreateApiFormModal({
|
const newTemplate = useCreateApiFormModal({
|
||||||
url: apiEndpoint,
|
url: apiEndpoint,
|
||||||
pathParams: { variant },
|
pathParams: { variant },
|
||||||
title: t`Create new` + ' ' + templateTypeTranslation,
|
title: t`Add new` + ' ' + templateTypeTranslation,
|
||||||
fields: {
|
fields: {
|
||||||
name: {},
|
name: {},
|
||||||
description: {},
|
description: {},
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Group, Text } from '@mantine/core';
|
import { Group, Text } from '@mantine/core';
|
||||||
import { ReactNode, useMemo } from 'react';
|
import { ReactNode, useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
import { ActionDropdown } from '../../components/items/ActionDropdown';
|
import { ActionDropdown } from '../../components/items/ActionDropdown';
|
||||||
@ -22,14 +21,15 @@ import {
|
|||||||
useTransferStockItem
|
useTransferStockItem
|
||||||
} from '../../forms/StockForms';
|
} from '../../forms/StockForms';
|
||||||
import { InvenTreeIcon } from '../../functions/icons';
|
import { InvenTreeIcon } from '../../functions/icons';
|
||||||
import { getDetailUrl } from '../../functions/urls';
|
|
||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import { TableColumn } from '../Column';
|
import { TableColumn } from '../Column';
|
||||||
import {
|
import {
|
||||||
|
DateColumn,
|
||||||
DescriptionColumn,
|
DescriptionColumn,
|
||||||
|
LocationColumn,
|
||||||
PartColumn,
|
PartColumn,
|
||||||
StatusColumn
|
StatusColumn
|
||||||
} from '../ColumnRenderers';
|
} from '../ColumnRenderers';
|
||||||
@ -55,7 +55,7 @@ function stockItemTableColumns(): TableColumn[] {
|
|||||||
ordering: 'stock',
|
ordering: 'stock',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
title: t`Stock`,
|
title: t`Stock`,
|
||||||
render: (record) => {
|
render: (record: any) => {
|
||||||
// TODO: Push this out into a custom renderer
|
// TODO: Push this out into a custom renderer
|
||||||
let quantity = record?.quantity ?? 0;
|
let quantity = record?.quantity ?? 0;
|
||||||
let allocated = record?.allocated ?? 0;
|
let allocated = record?.allocated ?? 0;
|
||||||
@ -198,16 +198,14 @@ function stockItemTableColumns(): TableColumn[] {
|
|||||||
accessor: 'batch',
|
accessor: 'batch',
|
||||||
sortable: true
|
sortable: true
|
||||||
},
|
},
|
||||||
{
|
LocationColumn({
|
||||||
accessor: 'location',
|
accessor: 'location_detail'
|
||||||
sortable: true,
|
}),
|
||||||
render: function (record: any) {
|
DateColumn({
|
||||||
// TODO: Custom renderer for location
|
accessor: 'stocktake_date',
|
||||||
// TODO: Note, if not "In stock" we don't want to display the actual location here
|
title: t`Stocktake`,
|
||||||
return record?.location_detail?.pathstring ?? record.location ?? '-';
|
sortable: true
|
||||||
}
|
}),
|
||||||
},
|
|
||||||
// TODO: stocktake column
|
|
||||||
{
|
{
|
||||||
accessor: 'expiry_date',
|
accessor: 'expiry_date',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
@ -357,8 +355,6 @@ export function StockItemTable({
|
|||||||
const table = useTable(tableName);
|
const table = useTable(tableName);
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const tableActionParams: StockOperationProps = useMemo(() => {
|
const tableActionParams: StockOperationProps = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
items: table.selectedRecords,
|
items: table.selectedRecords,
|
||||||
@ -377,11 +373,8 @@ export function StockItemTable({
|
|||||||
part: params.part,
|
part: params.part,
|
||||||
location: params.location
|
location: params.location
|
||||||
},
|
},
|
||||||
onFormSuccess: (data: any) => {
|
follow: true,
|
||||||
if (data.pk) {
|
modelType: ModelType.stockitem
|
||||||
navigate(getDetailUrl(ModelType.stockitem, data.pk));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const transferStock = useTransferStockItem(tableActionParams);
|
const transferStock = useTransferStockItem(tableActionParams);
|
||||||
|
Loading…
Reference in New Issue
Block a user