327 lines
8.0 KiB
TypeScript
327 lines
8.0 KiB
TypeScript
import { t } from '@lingui/macro';
|
|
import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
|
|
import {
|
|
IconDots,
|
|
IconInfoCircle,
|
|
IconList,
|
|
IconNotes,
|
|
IconPaperclip,
|
|
IconTools,
|
|
IconTruckDelivery,
|
|
IconTruckLoading
|
|
} from '@tabler/icons-react';
|
|
import { ReactNode, useMemo } from 'react';
|
|
import { useParams } from 'react-router-dom';
|
|
|
|
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
|
import { DetailsImage } from '../../components/details/DetailsImage';
|
|
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
|
import {
|
|
ActionDropdown,
|
|
DeleteItemAction,
|
|
EditItemAction
|
|
} from '../../components/items/ActionDropdown';
|
|
import { PageDetail } from '../../components/nav/PageDetail';
|
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
|
import { StatusRenderer } from '../../components/render/StatusRenderer';
|
|
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|
import { ModelType } from '../../enums/ModelType';
|
|
import { UserRoles } from '../../enums/Roles';
|
|
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
|
|
import { useEditApiFormModal } from '../../hooks/UseForm';
|
|
import { useInstance } from '../../hooks/UseInstance';
|
|
import { apiUrl } from '../../states/ApiState';
|
|
import { useUserState } from '../../states/UserState';
|
|
import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
|
|
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
|
|
|
/**
|
|
* Detail page for a single SalesOrder
|
|
*/
|
|
export default function SalesOrderDetail() {
|
|
const { id } = useParams();
|
|
|
|
const user = useUserState();
|
|
|
|
const {
|
|
instance: order,
|
|
instanceQuery,
|
|
refreshInstance
|
|
} = useInstance({
|
|
endpoint: ApiEndpoints.sales_order_list,
|
|
pk: id,
|
|
params: {
|
|
customer_detail: true
|
|
}
|
|
});
|
|
|
|
const detailsPanel = useMemo(() => {
|
|
if (instanceQuery.isFetching) {
|
|
return <Skeleton />;
|
|
}
|
|
|
|
let tl: DetailsField[] = [
|
|
{
|
|
type: 'text',
|
|
name: 'reference',
|
|
label: t`Reference`,
|
|
copy: true
|
|
},
|
|
{
|
|
type: 'text',
|
|
name: 'customer_reference',
|
|
label: t`Customer Reference`,
|
|
copy: true,
|
|
hidden: !order.customer_reference
|
|
},
|
|
{
|
|
type: 'link',
|
|
name: 'customer',
|
|
icon: 'customers',
|
|
label: t`Customer`,
|
|
model: ModelType.company
|
|
},
|
|
{
|
|
type: 'text',
|
|
name: 'description',
|
|
label: t`Description`,
|
|
copy: true
|
|
},
|
|
{
|
|
type: 'status',
|
|
name: 'status',
|
|
label: t`Status`,
|
|
model: ModelType.salesorder
|
|
}
|
|
];
|
|
|
|
let tr: DetailsField[] = [
|
|
{
|
|
type: 'text',
|
|
name: 'line_items',
|
|
label: t`Line Items`,
|
|
icon: 'list'
|
|
},
|
|
{
|
|
type: 'progressbar',
|
|
name: 'completed',
|
|
icon: 'progress',
|
|
label: t`Completed Line Items`,
|
|
total: order.line_items,
|
|
progress: order.completed_lines
|
|
},
|
|
{
|
|
type: 'progressbar',
|
|
name: 'shipments',
|
|
icon: 'shipment',
|
|
label: t`Completed Shipments`,
|
|
total: order.shipments,
|
|
progress: order.completed_shipments
|
|
// TODO: Fix this progress bar
|
|
},
|
|
{
|
|
type: 'text',
|
|
name: 'currency',
|
|
label: t`Order Currency,`
|
|
},
|
|
{
|
|
type: 'text',
|
|
name: 'total_cost',
|
|
label: t`Total Cost`
|
|
// TODO: Implement this!
|
|
}
|
|
];
|
|
|
|
let bl: DetailsField[] = [
|
|
{
|
|
type: 'link',
|
|
external: true,
|
|
name: 'link',
|
|
label: t`Link`,
|
|
copy: true,
|
|
hidden: !order.link
|
|
},
|
|
{
|
|
type: 'link',
|
|
model: ModelType.contact,
|
|
link: false,
|
|
name: 'contact',
|
|
label: t`Contact`,
|
|
icon: 'user',
|
|
copy: true,
|
|
hidden: !order.contact
|
|
}
|
|
// TODO: Project code
|
|
];
|
|
|
|
let br: DetailsField[] = [
|
|
{
|
|
type: 'text',
|
|
name: 'creation_date',
|
|
label: t`Created On`,
|
|
icon: 'calendar'
|
|
},
|
|
{
|
|
type: 'text',
|
|
name: 'target_date',
|
|
label: t`Target Date`,
|
|
icon: 'calendar',
|
|
hidden: !order.target_date
|
|
},
|
|
{
|
|
type: 'text',
|
|
name: 'responsible',
|
|
label: t`Responsible`,
|
|
badge: 'owner',
|
|
hidden: !order.responsible
|
|
}
|
|
];
|
|
|
|
return (
|
|
<ItemDetailsGrid>
|
|
<Grid>
|
|
<Grid.Col span={4}>
|
|
<DetailsImage
|
|
appRole={UserRoles.purchase_order}
|
|
apiPath={ApiEndpoints.company_list}
|
|
src={order.customer_detail?.image}
|
|
pk={order.customer}
|
|
/>
|
|
</Grid.Col>
|
|
<Grid.Col span={8}>
|
|
<DetailsTable fields={tl} item={order} />
|
|
</Grid.Col>
|
|
</Grid>
|
|
<DetailsTable fields={tr} item={order} />
|
|
<DetailsTable fields={bl} item={order} />
|
|
<DetailsTable fields={br} item={order} />
|
|
</ItemDetailsGrid>
|
|
);
|
|
}, [order, instanceQuery]);
|
|
|
|
const salesOrderFields = useSalesOrderFields();
|
|
|
|
const editSalesOrder = useEditApiFormModal({
|
|
url: ApiEndpoints.sales_order_list,
|
|
pk: order.pk,
|
|
title: t`Edit Sales Order`,
|
|
fields: salesOrderFields,
|
|
onFormSuccess: () => {
|
|
refreshInstance();
|
|
}
|
|
});
|
|
|
|
const orderPanels: PanelType[] = useMemo(() => {
|
|
return [
|
|
{
|
|
name: 'detail',
|
|
label: t`Order Details`,
|
|
icon: <IconInfoCircle />,
|
|
content: detailsPanel
|
|
},
|
|
{
|
|
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 />,
|
|
content: order?.pk ? (
|
|
<BuildOrderTable salesOrderId={order.pk} />
|
|
) : (
|
|
<Skeleton />
|
|
)
|
|
},
|
|
{
|
|
name: 'attachments',
|
|
label: t`Attachments`,
|
|
icon: <IconPaperclip />,
|
|
content: (
|
|
<AttachmentTable
|
|
endpoint={ApiEndpoints.sales_order_attachment_list}
|
|
model="order"
|
|
pk={Number(id)}
|
|
/>
|
|
)
|
|
},
|
|
{
|
|
name: 'notes',
|
|
label: t`Notes`,
|
|
icon: <IconNotes />,
|
|
content: (
|
|
<NotesEditor
|
|
url={apiUrl(ApiEndpoints.sales_order_list, id)}
|
|
data={order.notes ?? ''}
|
|
allowEdit={true}
|
|
/>
|
|
)
|
|
}
|
|
];
|
|
}, [order, id]);
|
|
|
|
const soActions = useMemo(() => {
|
|
return [
|
|
<ActionDropdown
|
|
key="order-actions"
|
|
tooltip={t`Order Actions`}
|
|
icon={<IconDots />}
|
|
actions={[
|
|
EditItemAction({
|
|
hidden: !user.hasChangeRole(UserRoles.sales_order),
|
|
onClick: () => {
|
|
editSalesOrder.open();
|
|
}
|
|
}),
|
|
DeleteItemAction({
|
|
hidden: !user.hasDeleteRole(UserRoles.sales_order)
|
|
// TODO: Delete?
|
|
})
|
|
]}
|
|
/>
|
|
];
|
|
}, [user]);
|
|
|
|
const orderBadges: ReactNode[] = useMemo(() => {
|
|
return instanceQuery.isLoading
|
|
? []
|
|
: [
|
|
<StatusRenderer
|
|
status={order.status}
|
|
type={ModelType.salesorder}
|
|
options={{ size: 'lg' }}
|
|
/>
|
|
];
|
|
}, [order, instanceQuery]);
|
|
|
|
return (
|
|
<>
|
|
{editSalesOrder.modal}
|
|
<Stack spacing="xs">
|
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
|
<PageDetail
|
|
title={t`Sales Order` + `: ${order.reference}`}
|
|
subtitle={order.description}
|
|
imageUrl={order.customer_detail?.image}
|
|
badges={orderBadges}
|
|
actions={soActions}
|
|
breadcrumbs={[{ name: t`Sales`, url: '/sales/' }]}
|
|
/>
|
|
<PanelGroup pageKey="salesorder" panels={orderPanels} />
|
|
</Stack>
|
|
</>
|
|
);
|
|
}
|