mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
React interface updates (#5798)
* Fix for <ActionDropdown> component - Ensure component key is set properly * Update <PageDetail> component - Consolidate and simplify * Update proxy settings for react development * Fixes for StatusRenderer component - Cannot use state hook inside function * Add PurchaseOrderDetail page * Tweak ApiImage component * Add "ReceivedStock" table to PurchaseOrder detail page * Add SalesOrderDetail page * Add ReturnOrderDetail page * Cleanup unused variables * Remove import for unused icon
This commit is contained in:
parent
22e9b14743
commit
0acfaced83
@ -56,7 +56,7 @@ export function ApiImage(props: ImageProps) {
|
|||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<LoadingOverlay visible={imgQuery.isLoading || imgQuery.isFetching} />
|
<LoadingOverlay visible={imgQuery.isLoading || imgQuery.isFetching} />
|
||||||
<Image {...props} src={image} />
|
<Image {...props} src={image} withPlaceholder fit="contain" />
|
||||||
{imgQuery.isError && <Overlay color="#F00" />}
|
{imgQuery.isError && <Overlay color="#F00" />}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
@ -39,12 +39,12 @@ export function ActionDropdown({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
<Menu.Dropdown>
|
<Menu.Dropdown>
|
||||||
{actions.map((action, index) =>
|
{actions.map((action) =>
|
||||||
action.disabled ? null : (
|
action.disabled ? null : (
|
||||||
<Tooltip label={action.tooltip}>
|
<Tooltip label={action.tooltip} key={`tooltip-${action.name}`}>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
icon={action.icon}
|
icon={action.icon}
|
||||||
key={index}
|
key={action.name}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (action.onClick != undefined) {
|
if (action.onClick != undefined) {
|
||||||
action.onClick();
|
action.onClick();
|
||||||
|
@ -14,7 +14,7 @@ export function BreadcrumbList({ breadcrumbs }: { breadcrumbs: Breadcrumb[] }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper p="3" radius="xs">
|
<Paper p="3" radius="xs">
|
||||||
<Breadcrumbs>
|
<Breadcrumbs separator=">">
|
||||||
{breadcrumbs.map((breadcrumb, index) => {
|
{breadcrumbs.map((breadcrumb, index) => {
|
||||||
return (
|
return (
|
||||||
<Anchor
|
<Anchor
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Group, Paper, Space, Stack, Text } from '@mantine/core';
|
import { Group, Paper, Space, Stack, Text } from '@mantine/core';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { ApiImage } from '../images/ApiImage';
|
||||||
import { StylishText } from '../items/StylishText';
|
import { StylishText } from '../items/StylishText';
|
||||||
import { Breadcrumb, BreadcrumbList } from './BreadcrumbList';
|
import { Breadcrumb, BreadcrumbList } from './BreadcrumbList';
|
||||||
|
|
||||||
@ -14,11 +15,13 @@ export function PageDetail({
|
|||||||
title,
|
title,
|
||||||
subtitle,
|
subtitle,
|
||||||
detail,
|
detail,
|
||||||
|
imageUrl,
|
||||||
breadcrumbs,
|
breadcrumbs,
|
||||||
actions
|
actions
|
||||||
}: {
|
}: {
|
||||||
title?: string;
|
title?: string;
|
||||||
subtitle?: string;
|
subtitle?: string;
|
||||||
|
imageUrl?: string;
|
||||||
detail?: ReactNode;
|
detail?: ReactNode;
|
||||||
breadcrumbs?: Breadcrumb[];
|
breadcrumbs?: Breadcrumb[];
|
||||||
actions?: ReactNode[];
|
actions?: ReactNode[];
|
||||||
@ -32,15 +35,23 @@ export function PageDetail({
|
|||||||
)}
|
)}
|
||||||
<Paper p="xs" radius="xs" shadow="xs">
|
<Paper p="xs" radius="xs" shadow="xs">
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<Group position="apart">
|
<Group position="apart" noWrap={true}>
|
||||||
<Group position="left">
|
<Group position="left" noWrap={true}>
|
||||||
|
{imageUrl && (
|
||||||
|
<ApiImage src={imageUrl} radius="sm" height={64} width={64} />
|
||||||
|
)}
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
{title && <StylishText size="xl">{title}</StylishText>}
|
{title && <StylishText size="lg">{title}</StylishText>}
|
||||||
{subtitle && <Text size="lg">{subtitle}</Text>}
|
{subtitle && (
|
||||||
{detail}
|
<Text size="md" truncate>
|
||||||
|
{subtitle}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Group>
|
</Group>
|
||||||
<Space />
|
<Space />
|
||||||
|
{detail}
|
||||||
|
<Space />
|
||||||
{actions && (
|
{actions && (
|
||||||
<Group spacing={5} position="right">
|
<Group spacing={5} position="right">
|
||||||
{actions}
|
{actions}
|
||||||
|
@ -72,11 +72,18 @@ export const StatusRenderer = ({
|
|||||||
type: ModelType;
|
type: ModelType;
|
||||||
options?: renderStatusLabelOptionsInterface;
|
options?: renderStatusLabelOptionsInterface;
|
||||||
}) => {
|
}) => {
|
||||||
const [statusCodeList] = useServerApiState((state) => [state.status]);
|
const statusCodeList = useServerApiState.getState().status;
|
||||||
|
|
||||||
|
if (status === undefined) {
|
||||||
|
console.log('StatusRenderer: status is undefined');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (statusCodeList === undefined) {
|
if (statusCodeList === undefined) {
|
||||||
console.log('StatusRenderer: statusCodeList is undefined');
|
console.log('StatusRenderer: statusCodeList is undefined');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusCodes = statusCodeList[type];
|
const statusCodes = statusCodeList[type];
|
||||||
if (statusCodes === undefined) {
|
if (statusCodes === undefined) {
|
||||||
console.log('StatusRenderer: statusCodes is undefined');
|
console.log('StatusRenderer: statusCodes is undefined');
|
||||||
|
@ -423,12 +423,13 @@ export function InvenTreeTable({
|
|||||||
/>
|
/>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Group position="apart">
|
<Group position="apart">
|
||||||
<Group position="left" spacing={5}>
|
<Group position="left" key="custom-actions" spacing={5}>
|
||||||
{tableProps.customActionGroups?.map(
|
{tableProps.customActionGroups?.map(
|
||||||
(group: any, idx: number) => group
|
(group: any, idx: number) => group
|
||||||
)}
|
)}
|
||||||
{(tableProps.barcodeActions?.length ?? 0 > 0) && (
|
{(tableProps.barcodeActions?.length ?? 0 > 0) && (
|
||||||
<ButtonMenu
|
<ButtonMenu
|
||||||
|
key="barcode-actions"
|
||||||
icon={<IconBarcode />}
|
icon={<IconBarcode />}
|
||||||
label={t`Barcode actions`}
|
label={t`Barcode actions`}
|
||||||
tooltip={t`Barcode actions`}
|
tooltip={t`Barcode actions`}
|
||||||
@ -437,6 +438,7 @@ export function InvenTreeTable({
|
|||||||
)}
|
)}
|
||||||
{(tableProps.printingActions?.length ?? 0 > 0) && (
|
{(tableProps.printingActions?.length ?? 0 > 0) && (
|
||||||
<ButtonMenu
|
<ButtonMenu
|
||||||
|
key="printing-actions"
|
||||||
icon={<IconPrinter />}
|
icon={<IconPrinter />}
|
||||||
label={t`Print actions`}
|
label={t`Print actions`}
|
||||||
tooltip={t`Print actions`}
|
tooltip={t`Print actions`}
|
||||||
@ -444,7 +446,10 @@ export function InvenTreeTable({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{tableProps.enableDownload && (
|
{tableProps.enableDownload && (
|
||||||
<DownloadAction downloadCallback={downloadData} />
|
<DownloadAction
|
||||||
|
key="download-action"
|
||||||
|
downloadCallback={downloadData}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
<Space />
|
<Space />
|
||||||
|
@ -181,7 +181,7 @@ export function AttachmentTable({
|
|||||||
|
|
||||||
if (allowEdit) {
|
if (allowEdit) {
|
||||||
actions.push(
|
actions.push(
|
||||||
<Tooltip label={t`Add attachment`}>
|
<Tooltip label={t`Add attachment`} key="attachment-add">
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
radius="sm"
|
radius="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -200,7 +200,7 @@ export function AttachmentTable({
|
|||||||
);
|
);
|
||||||
|
|
||||||
actions.push(
|
actions.push(
|
||||||
<Tooltip label={t`Add external link`}>
|
<Tooltip label={t`Add external link`} key="link-add">
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
radius="sm"
|
radius="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -226,6 +226,7 @@ export function AttachmentTable({
|
|||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
{pk && pk > 0 && (
|
{pk && pk > 0 && (
|
||||||
<InvenTreeTable
|
<InvenTreeTable
|
||||||
|
key="attachment-table"
|
||||||
url={url}
|
url={url}
|
||||||
tableKey={tableKey}
|
tableKey={tableKey}
|
||||||
columns={tableColumns}
|
columns={tableColumns}
|
||||||
@ -241,7 +242,7 @@ export function AttachmentTable({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{allowEdit && validPk && (
|
{allowEdit && validPk && (
|
||||||
<Dropzone onDrop={uploadFiles}>
|
<Dropzone onDrop={uploadFiles} key="attachment-dropzone">
|
||||||
<Dropzone.Idle>
|
<Dropzone.Idle>
|
||||||
<Group position="center">
|
<Group position="center">
|
||||||
<IconFileUpload size={24} />
|
<IconFileUpload size={24} />
|
||||||
|
@ -67,9 +67,11 @@ export function CompanyTable({
|
|||||||
...params
|
...params
|
||||||
},
|
},
|
||||||
onRowClick: (row: any) => {
|
onRowClick: (row: any) => {
|
||||||
|
if (row.pk) {
|
||||||
let base = path ?? 'company';
|
let base = path ?? 'company';
|
||||||
navigate(`/${base}/${row.pk}`);
|
navigate(`/${base}/${row.pk}`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1,6 +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 { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||||
@ -9,7 +10,12 @@ import { ModelType } from '../../render/ModelType';
|
|||||||
import { StatusRenderer } from '../../renderers/StatusRenderer';
|
import { StatusRenderer } from '../../renderers/StatusRenderer';
|
||||||
import { InvenTreeTable } from '../InvenTreeTable';
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a table of purchase orders
|
||||||
|
*/
|
||||||
export function PurchaseOrderTable({ params }: { params?: any }) {
|
export function PurchaseOrderTable({ params }: { params?: any }) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { tableKey } = useTableRefresh('purchase-order');
|
const { tableKey } = useTableRefresh('purchase-order');
|
||||||
|
|
||||||
// TODO: Custom filters
|
// TODO: Custom filters
|
||||||
@ -100,6 +106,11 @@ export function PurchaseOrderTable({ params }: { params?: any }) {
|
|||||||
params: {
|
params: {
|
||||||
...params,
|
...params,
|
||||||
supplier_detail: true
|
supplier_detail: true
|
||||||
|
},
|
||||||
|
onRowClick: (row: any) => {
|
||||||
|
if (row.pk) {
|
||||||
|
navigate(`/purchasing/purchase-order/${row.pk}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +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 { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||||
@ -12,6 +13,8 @@ import { InvenTreeTable } from '../InvenTreeTable';
|
|||||||
export function ReturnOrderTable({ params }: { params?: any }) {
|
export function ReturnOrderTable({ params }: { params?: any }) {
|
||||||
const { tableKey } = useTableRefresh('return-orders');
|
const { tableKey } = useTableRefresh('return-orders');
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// TODO: Custom filters
|
// TODO: Custom filters
|
||||||
|
|
||||||
// TODO: Row actions
|
// TODO: Row actions
|
||||||
@ -80,6 +83,11 @@ export function ReturnOrderTable({ params }: { params?: any }) {
|
|||||||
params: {
|
params: {
|
||||||
...params,
|
...params,
|
||||||
customer_detail: true
|
customer_detail: true
|
||||||
|
},
|
||||||
|
onRowClick: (row: any) => {
|
||||||
|
if (row.pk) {
|
||||||
|
navigate(`/sales/return-order/${row.pk}/`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +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 { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||||
@ -12,6 +13,8 @@ import { InvenTreeTable } from '../InvenTreeTable';
|
|||||||
export function SalesOrderTable({ params }: { params?: any }) {
|
export function SalesOrderTable({ params }: { params?: any }) {
|
||||||
const { tableKey } = useTableRefresh('sales-order');
|
const { tableKey } = useTableRefresh('sales-order');
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// TODO: Custom filters
|
// TODO: Custom filters
|
||||||
|
|
||||||
// TODO: Row actions
|
// TODO: Row actions
|
||||||
@ -82,6 +85,11 @@ export function SalesOrderTable({ params }: { params?: any }) {
|
|||||||
params: {
|
params: {
|
||||||
...params,
|
...params,
|
||||||
customer_detail: true
|
customer_detail: true
|
||||||
|
},
|
||||||
|
onRowClick: (row: any) => {
|
||||||
|
if (row.pk) {
|
||||||
|
navigate(`/sales/sales-order/${row.pk}/`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -70,7 +70,8 @@ function stockItemTableColumns(): TableColumn[] {
|
|||||||
title: t`Location`,
|
title: t`Location`,
|
||||||
render: function (record: any) {
|
render: function (record: any) {
|
||||||
// TODO: Custom renderer for location
|
// TODO: Custom renderer for location
|
||||||
return record.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
|
// TODO: stocktake column
|
||||||
@ -142,7 +143,8 @@ export function StockItemTable({ params = {} }: { params?: any }) {
|
|||||||
params: {
|
params: {
|
||||||
...params,
|
...params,
|
||||||
part_detail: true,
|
part_detail: true,
|
||||||
location_detail: true
|
location_detail: true,
|
||||||
|
supplier_part_detail: true
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -19,7 +19,7 @@ export function useInstance({
|
|||||||
params = {},
|
params = {},
|
||||||
defaultValue = {},
|
defaultValue = {},
|
||||||
hasPrimaryKey = true,
|
hasPrimaryKey = true,
|
||||||
refetchOnMount = false,
|
refetchOnMount = true,
|
||||||
refetchOnWindowFocus = false
|
refetchOnWindowFocus = false
|
||||||
}: {
|
}: {
|
||||||
endpoint: ApiPaths;
|
endpoint: ApiPaths;
|
||||||
@ -36,7 +36,7 @@ export function useInstance({
|
|||||||
queryKey: ['instance', endpoint, pk, params],
|
queryKey: ['instance', endpoint, pk, params],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (hasPrimaryKey) {
|
if (hasPrimaryKey) {
|
||||||
if (pk == null || pk == undefined || pk.length == 0) {
|
if (pk == null || pk == undefined || pk.length == 0 || pk == '-1') {
|
||||||
setInstance(defaultValue);
|
setInstance(defaultValue);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Alert, LoadingOverlay, Stack, Text } from '@mantine/core';
|
import { Group, LoadingOverlay, Stack, Table } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
IconClipboardCheck,
|
IconClipboardCheck,
|
||||||
IconClipboardList,
|
IconClipboardList,
|
||||||
@ -23,12 +23,10 @@ import { useMemo } from 'react';
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { ActionDropdown } from '../../components/items/ActionDropdown';
|
import { ActionDropdown } from '../../components/items/ActionDropdown';
|
||||||
import {
|
|
||||||
PlaceholderPanel,
|
|
||||||
PlaceholderPill
|
|
||||||
} from '../../components/items/Placeholder';
|
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
|
import { ModelType } from '../../components/render/ModelType';
|
||||||
|
import { StatusRenderer } from '../../components/renderers/StatusRenderer';
|
||||||
import { BuildOrderTable } from '../../components/tables/build/BuildOrderTable';
|
import { BuildOrderTable } from '../../components/tables/build/BuildOrderTable';
|
||||||
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
|
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
|
||||||
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
||||||
@ -43,6 +41,8 @@ import { useUserState } from '../../states/UserState';
|
|||||||
export default function BuildDetail() {
|
export default function BuildDetail() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const user = useUserState();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
instance: build,
|
instance: build,
|
||||||
refreshInstance,
|
refreshInstance,
|
||||||
@ -52,37 +52,65 @@ export default function BuildDetail() {
|
|||||||
pk: id,
|
pk: id,
|
||||||
params: {
|
params: {
|
||||||
part_detail: true
|
part_detail: true
|
||||||
}
|
},
|
||||||
|
refetchOnMount: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = useUserState();
|
const buildDetailsPanel = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<Group position="apart" grow>
|
||||||
|
<Table striped>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{t`Base Part`}</td>
|
||||||
|
<td>{build.part_detail?.name}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{t`Quantity`}</td>
|
||||||
|
<td>{build.quantity}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{t`Build Status`}</td>
|
||||||
|
<td>
|
||||||
|
{build.status && (
|
||||||
|
<StatusRenderer
|
||||||
|
status={build.status}
|
||||||
|
type={ModelType.build}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
<Table></Table>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}, [build]);
|
||||||
|
|
||||||
const buildPanels: PanelType[] = useMemo(() => {
|
const buildPanels: PanelType[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'details',
|
name: 'details',
|
||||||
label: t`Build Details`,
|
label: t`Build Details`,
|
||||||
icon: <IconInfoCircle size="18" />,
|
icon: <IconInfoCircle />,
|
||||||
content: <PlaceholderPanel />
|
content: buildDetailsPanel
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'allocate-stock',
|
name: 'allocate-stock',
|
||||||
label: t`Allocate Stock`,
|
label: t`Allocate Stock`,
|
||||||
icon: <IconListCheck size="18" />,
|
icon: <IconListCheck />
|
||||||
content: <PlaceholderPanel />
|
|
||||||
// TODO: Hide if build is complete
|
// TODO: Hide if build is complete
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'incomplete-outputs',
|
name: 'incomplete-outputs',
|
||||||
label: t`Incomplete Outputs`,
|
label: t`Incomplete Outputs`,
|
||||||
icon: <IconClipboardList size="18" />,
|
icon: <IconClipboardList />
|
||||||
content: <PlaceholderPanel />
|
|
||||||
// TODO: Hide if build is complete
|
// TODO: Hide if build is complete
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'complete-outputs',
|
name: 'complete-outputs',
|
||||||
label: t`Completed Outputs`,
|
label: t`Completed Outputs`,
|
||||||
icon: <IconClipboardCheck size="18" />,
|
icon: <IconClipboardCheck />,
|
||||||
content: (
|
content: (
|
||||||
<StockItemTable
|
<StockItemTable
|
||||||
params={{
|
params={{
|
||||||
@ -95,7 +123,7 @@ export default function BuildDetail() {
|
|||||||
{
|
{
|
||||||
name: 'consumed-stock',
|
name: 'consumed-stock',
|
||||||
label: t`Consumed Stock`,
|
label: t`Consumed Stock`,
|
||||||
icon: <IconList size="18" />,
|
icon: <IconList />,
|
||||||
content: (
|
content: (
|
||||||
<StockItemTable
|
<StockItemTable
|
||||||
params={{
|
params={{
|
||||||
@ -107,7 +135,7 @@ export default function BuildDetail() {
|
|||||||
{
|
{
|
||||||
name: 'child-orders',
|
name: 'child-orders',
|
||||||
label: t`Child Build Orders`,
|
label: t`Child Build Orders`,
|
||||||
icon: <IconSitemap size="18" />,
|
icon: <IconSitemap />,
|
||||||
content: (
|
content: (
|
||||||
<BuildOrderTable
|
<BuildOrderTable
|
||||||
params={{
|
params={{
|
||||||
@ -119,7 +147,7 @@ export default function BuildDetail() {
|
|||||||
{
|
{
|
||||||
name: 'attachments',
|
name: 'attachments',
|
||||||
label: t`Attachments`,
|
label: t`Attachments`,
|
||||||
icon: <IconPaperclip size="18" />,
|
icon: <IconPaperclip />,
|
||||||
content: (
|
content: (
|
||||||
<AttachmentTable
|
<AttachmentTable
|
||||||
endpoint={ApiPaths.build_order_attachment_list}
|
endpoint={ApiPaths.build_order_attachment_list}
|
||||||
@ -131,7 +159,7 @@ export default function BuildDetail() {
|
|||||||
{
|
{
|
||||||
name: 'notes',
|
name: 'notes',
|
||||||
label: t`Notes`,
|
label: t`Notes`,
|
||||||
icon: <IconNotes size="18" />,
|
icon: <IconNotes />,
|
||||||
content: (
|
content: (
|
||||||
<NotesEditor
|
<NotesEditor
|
||||||
url={apiUrl(ApiPaths.build_order_list, build.pk)}
|
url={apiUrl(ApiPaths.build_order_list, build.pk)}
|
||||||
@ -147,6 +175,7 @@ export default function BuildDetail() {
|
|||||||
// TODO: Disable certain actions based on user permissions
|
// TODO: Disable certain actions based on user permissions
|
||||||
return [
|
return [
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
|
key="barcode"
|
||||||
tooltip={t`Barcode Actions`}
|
tooltip={t`Barcode Actions`}
|
||||||
icon={<IconQrcode />}
|
icon={<IconQrcode />}
|
||||||
actions={[
|
actions={[
|
||||||
@ -170,6 +199,7 @@ export default function BuildDetail() {
|
|||||||
]}
|
]}
|
||||||
/>,
|
/>,
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
|
key="report"
|
||||||
tooltip={t`Reporting Actions`}
|
tooltip={t`Reporting Actions`}
|
||||||
icon={<IconPrinter />}
|
icon={<IconPrinter />}
|
||||||
actions={[
|
actions={[
|
||||||
@ -181,6 +211,7 @@ export default function BuildDetail() {
|
|||||||
]}
|
]}
|
||||||
/>,
|
/>,
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
|
key="build"
|
||||||
tooltip={t`Build Order Actions`}
|
tooltip={t`Build Order Actions`}
|
||||||
icon={<IconDots />}
|
icon={<IconDots />}
|
||||||
actions={[
|
actions={[
|
||||||
@ -204,19 +235,28 @@ export default function BuildDetail() {
|
|||||||
];
|
];
|
||||||
}, [id, build, user]);
|
}, [id, build, user]);
|
||||||
|
|
||||||
|
const buildDetail = useMemo(() => {
|
||||||
|
return StatusRenderer({
|
||||||
|
status: build.status,
|
||||||
|
type: ModelType.build
|
||||||
|
});
|
||||||
|
}, [build, id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={t`Build Order`}
|
title={build.reference}
|
||||||
subtitle={build.reference}
|
subtitle={build.title}
|
||||||
|
detail={buildDetail}
|
||||||
|
imageUrl={build.part_detail?.thumbnail}
|
||||||
breadcrumbs={[
|
breadcrumbs={[
|
||||||
{ name: t`Build Orders`, url: '/build' },
|
{ name: t`Build Orders`, url: '/build' },
|
||||||
{ name: build.reference, url: `/build/${build.pk}` }
|
{ name: build.reference, url: `/build/${build.pk}` }
|
||||||
]}
|
]}
|
||||||
actions={buildActions}
|
actions={buildActions}
|
||||||
/>
|
/>
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
|
||||||
<PanelGroup pageKey="build" panels={buildPanels} />
|
<PanelGroup pageKey="build" panels={buildPanels} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</>
|
</>
|
||||||
|
@ -15,7 +15,11 @@ export default function BuildIndex() {
|
|||||||
<PageDetail
|
<PageDetail
|
||||||
title={t`Build Orders`}
|
title={t`Build Orders`}
|
||||||
actions={[
|
actions={[
|
||||||
<Button color="green" onClick={() => notYetImplemented()}>
|
<Button
|
||||||
|
key="new-build"
|
||||||
|
color="green"
|
||||||
|
onClick={() => notYetImplemented()}
|
||||||
|
>
|
||||||
<Text>{t`New Build Order`}</Text>
|
<Text>{t`New Build Order`}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
]}
|
]}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Group, LoadingOverlay, Stack, Text } from '@mantine/core';
|
import { LoadingOverlay, Stack } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
IconBuildingFactory2,
|
IconBuildingFactory2,
|
||||||
IconBuildingWarehouse,
|
IconBuildingWarehouse,
|
||||||
@ -20,7 +20,6 @@ import {
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { Thumbnail } from '../../components/images/Thumbnail';
|
|
||||||
import { ActionDropdown } from '../../components/items/ActionDropdown';
|
import { ActionDropdown } from '../../components/items/ActionDropdown';
|
||||||
import { Breadcrumb } from '../../components/nav/BreadcrumbList';
|
import { Breadcrumb } from '../../components/nav/BreadcrumbList';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
@ -159,24 +158,6 @@ export default function CompanyDetail(props: CompanyDetailProps) {
|
|||||||
];
|
];
|
||||||
}, [id, company]);
|
}, [id, company]);
|
||||||
|
|
||||||
const companyDetail = useMemo(() => {
|
|
||||||
return (
|
|
||||||
<Group spacing="xs" noWrap={true}>
|
|
||||||
<Thumbnail
|
|
||||||
src={String(company.image || '')}
|
|
||||||
size={128}
|
|
||||||
alt={company?.name}
|
|
||||||
/>
|
|
||||||
<Stack spacing="xs">
|
|
||||||
<Text size="lg" weight={500}>
|
|
||||||
{company.name}
|
|
||||||
</Text>
|
|
||||||
<Text size="sm">{company.description}</Text>
|
|
||||||
</Stack>
|
|
||||||
</Group>
|
|
||||||
);
|
|
||||||
}, [id, company]);
|
|
||||||
|
|
||||||
const companyActions = useMemo(() => {
|
const companyActions = useMemo(() => {
|
||||||
// TODO: Finer fidelity on these permissions, perhaps?
|
// TODO: Finer fidelity on these permissions, perhaps?
|
||||||
let canEdit = user.checkUserRole('purchase_order', 'change');
|
let canEdit = user.checkUserRole('purchase_order', 'change');
|
||||||
@ -184,6 +165,7 @@ export default function CompanyDetail(props: CompanyDetailProps) {
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
|
key="company"
|
||||||
tooltip={t`Company Actions`}
|
tooltip={t`Company Actions`}
|
||||||
icon={<IconDots />}
|
icon={<IconDots />}
|
||||||
actions={[
|
actions={[
|
||||||
@ -216,8 +198,10 @@ export default function CompanyDetail(props: CompanyDetailProps) {
|
|||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<PageDetail
|
<PageDetail
|
||||||
detail={companyDetail}
|
title={t`Company` + `: ${company.name}`}
|
||||||
|
subtitle={company.description}
|
||||||
actions={companyActions}
|
actions={companyActions}
|
||||||
|
imageUrl={company.image}
|
||||||
breadcrumbs={props.breadcrumbs}
|
breadcrumbs={props.breadcrumbs}
|
||||||
/>
|
/>
|
||||||
<PanelGroup pageKey="company" panels={companyPanels} />
|
<PanelGroup pageKey="company" panels={companyPanels} />
|
||||||
|
@ -30,7 +30,6 @@ import {
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { ApiImage } from '../../components/images/ApiImage';
|
|
||||||
import { ActionDropdown } from '../../components/items/ActionDropdown';
|
import { ActionDropdown } from '../../components/items/ActionDropdown';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
@ -203,17 +202,8 @@ export default function PartDetail() {
|
|||||||
const partDetail = useMemo(() => {
|
const partDetail = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<Group spacing="xs" noWrap={true}>
|
<Group spacing="xs" noWrap={true}>
|
||||||
<ApiImage
|
|
||||||
src={String(part.image || '')}
|
|
||||||
radius="sm"
|
|
||||||
height={64}
|
|
||||||
width={64}
|
|
||||||
/>
|
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<Text size="lg" weight={500}>
|
<Text>Stock: {part.in_stock}</Text>
|
||||||
{part.full_name}
|
|
||||||
</Text>
|
|
||||||
<Text size="sm">{part.description}</Text>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
@ -223,6 +213,7 @@ export default function PartDetail() {
|
|||||||
// TODO: Disable actions based on user permissions
|
// TODO: Disable actions based on user permissions
|
||||||
return [
|
return [
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
|
key="barcode"
|
||||||
tooltip={t`Barcode Actions`}
|
tooltip={t`Barcode Actions`}
|
||||||
icon={<IconQrcode />}
|
icon={<IconQrcode />}
|
||||||
actions={[
|
actions={[
|
||||||
@ -246,6 +237,7 @@ export default function PartDetail() {
|
|||||||
]}
|
]}
|
||||||
/>,
|
/>,
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
|
key="stock"
|
||||||
tooltip={t`Stock Actions`}
|
tooltip={t`Stock Actions`}
|
||||||
icon={<IconPackages />}
|
icon={<IconPackages />}
|
||||||
actions={[
|
actions={[
|
||||||
@ -262,6 +254,7 @@ export default function PartDetail() {
|
|||||||
]}
|
]}
|
||||||
/>,
|
/>,
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
|
key="part"
|
||||||
tooltip={t`Part Actions`}
|
tooltip={t`Part Actions`}
|
||||||
icon={<IconDots />}
|
icon={<IconDots />}
|
||||||
actions={[
|
actions={[
|
||||||
@ -297,6 +290,9 @@ export default function PartDetail() {
|
|||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<PageDetail
|
<PageDetail
|
||||||
|
title={t`Part` + ': ' + part.full_name}
|
||||||
|
subtitle={part.description}
|
||||||
|
imageUrl={part.image}
|
||||||
detail={partDetail}
|
detail={partDetail}
|
||||||
breadcrumbs={breadcrumbs}
|
breadcrumbs={breadcrumbs}
|
||||||
actions={partActions}
|
actions={partActions}
|
||||||
|
101
src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx
Normal file
101
src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { LoadingOverlay, Stack } from '@mantine/core';
|
||||||
|
import {
|
||||||
|
IconInfoCircle,
|
||||||
|
IconList,
|
||||||
|
IconNotes,
|
||||||
|
IconPackages,
|
||||||
|
IconPaperclip
|
||||||
|
} from '@tabler/icons-react';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
|
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
|
||||||
|
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
||||||
|
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||||
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
|
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detail page for a single PurchaseOrder
|
||||||
|
*/
|
||||||
|
export default function PurchaseOrderDetail() {
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const { instance: order, instanceQuery } = useInstance({
|
||||||
|
endpoint: ApiPaths.purchase_order_list,
|
||||||
|
pk: id,
|
||||||
|
params: {
|
||||||
|
supplier_detail: true
|
||||||
|
},
|
||||||
|
refetchOnMount: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const orderPanels: PanelType[] = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'detail',
|
||||||
|
label: t`Order Details`,
|
||||||
|
icon: <IconInfoCircle />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'line-items',
|
||||||
|
label: t`Line Items`,
|
||||||
|
icon: <IconList />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'received-stock',
|
||||||
|
label: t`Received Stock`,
|
||||||
|
icon: <IconPackages />,
|
||||||
|
content: (
|
||||||
|
<StockItemTable
|
||||||
|
params={{
|
||||||
|
purchase_order: id
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'attachments',
|
||||||
|
label: t`Attachments`,
|
||||||
|
icon: <IconPaperclip />,
|
||||||
|
content: (
|
||||||
|
<AttachmentTable
|
||||||
|
endpoint={ApiPaths.purchase_order_attachment_list}
|
||||||
|
model="order"
|
||||||
|
pk={order.pk ?? -1}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'notes',
|
||||||
|
label: t`Notes`,
|
||||||
|
icon: <IconNotes />,
|
||||||
|
content: (
|
||||||
|
<NotesEditor
|
||||||
|
url={apiUrl(ApiPaths.purchase_order_list, order.pk)}
|
||||||
|
data={order.notes ?? ''}
|
||||||
|
allowEdit={true}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}, [order, id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack spacing="xs">
|
||||||
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
|
<PageDetail
|
||||||
|
title={t`Purchase Order` + `: ${order.reference}`}
|
||||||
|
subtitle={order.description}
|
||||||
|
imageUrl={order.supplier_detail?.image}
|
||||||
|
breadcrumbs={[{ name: t`Purchasing`, url: '/purchasing/' }]}
|
||||||
|
/>
|
||||||
|
<PanelGroup pageKey="purchaseorder" panels={orderPanels} />
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
76
src/frontend/src/pages/sales/ReturnOrderDetail.tsx
Normal file
76
src/frontend/src/pages/sales/ReturnOrderDetail.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { LoadingOverlay, Stack } from '@mantine/core';
|
||||||
|
import { IconInfoCircle, IconNotes, IconPaperclip } from '@tabler/icons-react';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
|
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
|
||||||
|
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||||
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
|
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detail page for a single ReturnOrder
|
||||||
|
*/
|
||||||
|
export default function ReturnOrderDetail() {
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const { instance: order, instanceQuery } = useInstance({
|
||||||
|
endpoint: ApiPaths.return_order_list,
|
||||||
|
pk: id,
|
||||||
|
params: {
|
||||||
|
customer_detail: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const orderPanels: PanelType[] = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'detail',
|
||||||
|
label: t`Order Details`,
|
||||||
|
icon: <IconInfoCircle />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'attachments',
|
||||||
|
label: t`Attachments`,
|
||||||
|
icon: <IconPaperclip />,
|
||||||
|
content: (
|
||||||
|
<AttachmentTable
|
||||||
|
endpoint={ApiPaths.return_order_attachment_list}
|
||||||
|
model="order"
|
||||||
|
pk={order.pk ?? -1}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'notes',
|
||||||
|
label: t`Notes`,
|
||||||
|
icon: <IconNotes />,
|
||||||
|
content: (
|
||||||
|
<NotesEditor
|
||||||
|
url={apiUrl(ApiPaths.return_order_list, order.pk)}
|
||||||
|
data={order.notes ?? ''}
|
||||||
|
allowEdit={true}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}, [order, id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack spacing="xs">
|
||||||
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
|
<PageDetail
|
||||||
|
title={t`Return Order` + `: ${order.reference}`}
|
||||||
|
subtitle={order.description}
|
||||||
|
imageUrl={order.customer_detail?.image}
|
||||||
|
breadcrumbs={[{ name: t`Sales`, url: '/sales/' }]}
|
||||||
|
/>
|
||||||
|
<PanelGroup pageKey="returnorder" panels={orderPanels} />
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
104
src/frontend/src/pages/sales/SalesOrderDetail.tsx
Normal file
104
src/frontend/src/pages/sales/SalesOrderDetail.tsx
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { LoadingOverlay, Stack } from '@mantine/core';
|
||||||
|
import {
|
||||||
|
IconInfoCircle,
|
||||||
|
IconList,
|
||||||
|
IconNotes,
|
||||||
|
IconPaperclip,
|
||||||
|
IconTools,
|
||||||
|
IconTruckDelivery,
|
||||||
|
IconTruckLoading
|
||||||
|
} from '@tabler/icons-react';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
|
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
|
||||||
|
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||||
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
|
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detail page for a single SalesOrder
|
||||||
|
*/
|
||||||
|
export default function SalesOrderDetail() {
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const { instance: order, instanceQuery } = useInstance({
|
||||||
|
endpoint: ApiPaths.sales_order_list,
|
||||||
|
pk: id,
|
||||||
|
params: {
|
||||||
|
customer_detail: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const orderPanels: PanelType[] = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'detail',
|
||||||
|
label: t`Order Details`,
|
||||||
|
icon: <IconInfoCircle />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'line-items',
|
||||||
|
label: t`Line Items`,
|
||||||
|
icon: <IconList />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pending-shipments',
|
||||||
|
label: t`Pending Shipments`,
|
||||||
|
icon: <IconTruckLoading />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'completed-shipments',
|
||||||
|
label: t`Completed Shipments`,
|
||||||
|
icon: <IconTruckDelivery />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'build-orders',
|
||||||
|
label: t`Build Orders`,
|
||||||
|
icon: <IconTools />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'attachments',
|
||||||
|
label: t`Attachments`,
|
||||||
|
icon: <IconPaperclip />,
|
||||||
|
content: (
|
||||||
|
<AttachmentTable
|
||||||
|
endpoint={ApiPaths.sales_order_attachment_list}
|
||||||
|
model="order"
|
||||||
|
pk={order.pk ?? -1}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'notes',
|
||||||
|
label: t`Notes`,
|
||||||
|
icon: <IconNotes />,
|
||||||
|
content: (
|
||||||
|
<NotesEditor
|
||||||
|
url={apiUrl(ApiPaths.sales_order_list, order.pk)}
|
||||||
|
data={order.notes ?? ''}
|
||||||
|
allowEdit={true}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}, [order, id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack spacing="xs">
|
||||||
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
|
<PageDetail
|
||||||
|
title={t`Sales Order` + `: ${order.reference}`}
|
||||||
|
subtitle={order.description}
|
||||||
|
imageUrl={order.customer_detail?.image}
|
||||||
|
breadcrumbs={[{ name: t`Sales`, url: '/sales/' }]}
|
||||||
|
/>
|
||||||
|
<PanelGroup pageKey="salesorder" panels={orderPanels} />
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -54,10 +54,22 @@ export const PurchasingIndex = Loadable(
|
|||||||
lazy(() => import('./pages/purchasing/PurchasingIndex'))
|
lazy(() => import('./pages/purchasing/PurchasingIndex'))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const PurchaseOrderDetail = Loadable(
|
||||||
|
lazy(() => import('./pages/purchasing/PurchaseOrderDetail'))
|
||||||
|
);
|
||||||
|
|
||||||
export const SalesIndex = Loadable(
|
export const SalesIndex = Loadable(
|
||||||
lazy(() => import('./pages/sales/SalesIndex'))
|
lazy(() => import('./pages/sales/SalesIndex'))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const SalesOrderDetail = Loadable(
|
||||||
|
lazy(() => import('./pages/sales/SalesOrderDetail'))
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ReturnOrderDetail = Loadable(
|
||||||
|
lazy(() => import('./pages/sales/ReturnOrderDetail'))
|
||||||
|
);
|
||||||
|
|
||||||
export const Scan = Loadable(lazy(() => import('./pages/Index/Scan')));
|
export const Scan = Loadable(lazy(() => import('./pages/Index/Scan')));
|
||||||
|
|
||||||
export const Dashboard = Loadable(
|
export const Dashboard = Loadable(
|
||||||
@ -125,12 +137,15 @@ export const routes = (
|
|||||||
</Route>
|
</Route>
|
||||||
<Route path="purchasing/">
|
<Route path="purchasing/">
|
||||||
<Route index element={<PurchasingIndex />} />
|
<Route index element={<PurchasingIndex />} />
|
||||||
|
<Route path="purchase-order/:id/" element={<PurchaseOrderDetail />} />
|
||||||
<Route path="supplier/:id/" element={<SupplierDetail />} />
|
<Route path="supplier/:id/" element={<SupplierDetail />} />
|
||||||
<Route path="manufacturer/:id/" element={<ManufacturerDetail />} />
|
<Route path="manufacturer/:id/" element={<ManufacturerDetail />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="company/:id/" element={<CompanyDetail />} />
|
<Route path="company/:id/" element={<CompanyDetail />} />
|
||||||
<Route path="sales/">
|
<Route path="sales/">
|
||||||
<Route index element={<SalesIndex />} />
|
<Route index element={<SalesIndex />} />
|
||||||
|
<Route path="sales-order/:id/" element={<SalesOrderDetail />} />
|
||||||
|
<Route path="return-order/:id/" element={<ReturnOrderDetail />} />
|
||||||
<Route path="customer/:id/" element={<CustomerDetail />} />
|
<Route path="customer/:id/" element={<CustomerDetail />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/profile/:tabValue" element={<Profile />} />
|
<Route path="/profile/:tabValue" element={<Profile />} />
|
||||||
|
@ -93,12 +93,15 @@ export enum ApiPaths {
|
|||||||
|
|
||||||
// Purchase Order URLs
|
// Purchase Order URLs
|
||||||
purchase_order_list = 'api-purchase-order-list',
|
purchase_order_list = 'api-purchase-order-list',
|
||||||
|
purchase_order_attachment_list = 'api-purchase-order-attachment-list',
|
||||||
|
|
||||||
// Sales Order URLs
|
// Sales Order URLs
|
||||||
sales_order_list = 'api-sales-order-list',
|
sales_order_list = 'api-sales-order-list',
|
||||||
|
sales_order_attachment_list = 'api-sales-order-attachment-list',
|
||||||
|
|
||||||
// Return Order URLs
|
// Return Order URLs
|
||||||
return_order_list = 'api-return-order-list',
|
return_order_list = 'api-return-order-list',
|
||||||
|
return_order_attachment_list = 'api-return-order-attachment-list',
|
||||||
|
|
||||||
// Plugin URLs
|
// Plugin URLs
|
||||||
plugin_list = 'api-plugin-list',
|
plugin_list = 'api-plugin-list',
|
||||||
@ -180,10 +183,16 @@ export function apiEndpoint(path: ApiPaths): string {
|
|||||||
return 'stock/attachment/';
|
return 'stock/attachment/';
|
||||||
case ApiPaths.purchase_order_list:
|
case ApiPaths.purchase_order_list:
|
||||||
return 'order/po/';
|
return 'order/po/';
|
||||||
|
case ApiPaths.purchase_order_attachment_list:
|
||||||
|
return 'order/po/attachment/';
|
||||||
case ApiPaths.sales_order_list:
|
case ApiPaths.sales_order_list:
|
||||||
return 'order/so/';
|
return 'order/so/';
|
||||||
|
case ApiPaths.sales_order_attachment_list:
|
||||||
|
return 'order/so/attachment/';
|
||||||
case ApiPaths.return_order_list:
|
case ApiPaths.return_order_list:
|
||||||
return 'order/ro/';
|
return 'order/ro/';
|
||||||
|
case ApiPaths.return_order_attachment_list:
|
||||||
|
return 'order/ro/attachment/';
|
||||||
case ApiPaths.plugin_list:
|
case ApiPaths.plugin_list:
|
||||||
return 'plugins/';
|
return 'plugins/';
|
||||||
case ApiPaths.project_code_list:
|
case ApiPaths.project_code_list:
|
||||||
|
@ -28,6 +28,16 @@ export default defineConfig({
|
|||||||
target: 'http://localhost:8000',
|
target: 'http://localhost:8000',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: true
|
secure: true
|
||||||
|
},
|
||||||
|
'/media': {
|
||||||
|
target: 'http://localhost:8000',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: true
|
||||||
|
},
|
||||||
|
'/static': {
|
||||||
|
target: 'http://localhost:8000',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
Loading…
Reference in New Issue
Block a user