mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
[PUI] stock item delete (#7204)
* Handle stock item delete in PUI * Support deletion of stock location * Delete part category * Some refactoring of the TableField approach - Still needs some work - Code can be made a lot cleaner here * Use mantine components * Fix incorrect import * Update ServerInfoModal * Further table refactoring * Implement delete part function
This commit is contained in:
parent
700a3612b7
commit
b5a3e4aac4
@ -32,7 +32,7 @@ export function ChoiceField({
|
||||
return choices.map((choice) => {
|
||||
return {
|
||||
value: choice.value.toString(),
|
||||
label: choice.display_name.toString()
|
||||
label: choice.display_name ?? choice.value
|
||||
};
|
||||
});
|
||||
}, [definition.choices]);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Trans, t } from '@lingui/macro';
|
||||
import { Table } from '@mantine/core';
|
||||
import { Container, Flex, Group, Table } from '@mantine/core';
|
||||
import { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||
|
||||
import { InvenTreeIcon } from '../../../functions/icons';
|
||||
@ -34,19 +34,21 @@ export function TableField({
|
||||
|
||||
return (
|
||||
<Table highlightOnHover striped>
|
||||
<thead>
|
||||
<tr>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
{definition.headers?.map((header) => {
|
||||
return <th key={header}>{header}</th>;
|
||||
})}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{value.length > 0 ? (
|
||||
value.map((item: any, idx: number) => {
|
||||
// Table fields require render function
|
||||
if (!definition.modelRenderer) {
|
||||
return <tr>{t`modelRenderer entry required for tables`}</tr>;
|
||||
return (
|
||||
<Table.Tr>{t`modelRenderer entry required for tables`}</Table.Tr>
|
||||
);
|
||||
}
|
||||
return definition.modelRenderer({
|
||||
item: item,
|
||||
@ -56,8 +58,8 @@ export function TableField({
|
||||
});
|
||||
})
|
||||
) : (
|
||||
<tr>
|
||||
<td
|
||||
<Table.Tr>
|
||||
<Table.Td
|
||||
style={{ textAlign: 'center' }}
|
||||
colSpan={definition.headers?.length}
|
||||
>
|
||||
@ -71,10 +73,36 @@ export function TableField({
|
||||
<InvenTreeIcon icon="info" />
|
||||
<Trans>No entries available</Trans>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
)}
|
||||
</tbody>
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Display an "extra" row below the main table row, for additional information.
|
||||
*/
|
||||
export function TableFieldExtraRow({
|
||||
visible,
|
||||
content,
|
||||
colSpan
|
||||
}: {
|
||||
visible: boolean;
|
||||
content: React.ReactNode;
|
||||
colSpan?: number;
|
||||
}) {
|
||||
return (
|
||||
visible && (
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={colSpan ?? 3}>
|
||||
<Group justify="flex-start" grow>
|
||||
<InvenTreeIcon icon="downright" />
|
||||
{content}
|
||||
</Group>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -189,10 +189,12 @@ export function EditItemAction({
|
||||
// Common action button for deleting an item
|
||||
export function DeleteItemAction({
|
||||
hidden = false,
|
||||
disabled = false,
|
||||
tooltip,
|
||||
onClick
|
||||
}: {
|
||||
hidden?: boolean;
|
||||
disabled?: boolean;
|
||||
tooltip?: string;
|
||||
onClick?: () => void;
|
||||
}): ActionDropdownItem {
|
||||
@ -201,7 +203,8 @@ export function DeleteItemAction({
|
||||
name: t`Delete`,
|
||||
tooltip: tooltip ?? t`Delete item`,
|
||||
onClick: onClick,
|
||||
hidden: hidden
|
||||
hidden: hidden,
|
||||
disabled: disabled
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -26,46 +26,46 @@ export function ServerInfoModal({
|
||||
<Trans>Server</Trans>
|
||||
</Title>
|
||||
<Table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Table.Tbody>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Trans>Instance Name</Trans>
|
||||
</td>
|
||||
<td>{server.instance}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>{server.instance}</Table.Td>
|
||||
</Table.Tr>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Trans>Database</Trans>
|
||||
</td>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<OnlyStaff>{server.database}</OnlyStaff>
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
{server.debug_mode && (
|
||||
<tr>
|
||||
<td>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Trans>Debug Mode</Trans>
|
||||
</td>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Trans>Server is running in debug mode</Trans>
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
)}
|
||||
{server.docker_mode && (
|
||||
<tr>
|
||||
<td>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Trans>Docker Mode</Trans>
|
||||
</td>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Trans>Server is deployed using docker</Trans>
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
)}
|
||||
<tr>
|
||||
<td>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Trans>Plugin Support</Trans>
|
||||
</td>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge color={server.plugins_enabled ? 'green' : 'red'}>
|
||||
{server.plugins_enabled ? (
|
||||
<Trans>Plugin support enabled</Trans>
|
||||
@ -73,13 +73,13 @@ export function ServerInfoModal({
|
||||
<Trans>Plugin support disabled</Trans>
|
||||
)}
|
||||
</Badge>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Trans>Server status</Trans>
|
||||
</td>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<OnlyStaff>
|
||||
<Badge color={server.system_health ? 'green' : 'yellow'}>
|
||||
{server.system_health ? (
|
||||
@ -89,52 +89,52 @@ export function ServerInfoModal({
|
||||
)}
|
||||
</Badge>
|
||||
</OnlyStaff>
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
{server.worker_running != true && (
|
||||
<tr>
|
||||
<td>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Trans>Background Worker</Trans>
|
||||
</td>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge color="red">
|
||||
<Trans>Background worker not running</Trans>
|
||||
</Badge>
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
)}
|
||||
{server.email_configured != true && (
|
||||
<tr>
|
||||
<td>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Trans>Email Settings</Trans>
|
||||
</td>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge color="red">
|
||||
<Trans>Email settings not configured</Trans>
|
||||
</Badge>
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
)}
|
||||
</tbody>
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
<Title order={5}>
|
||||
<Trans>Version</Trans>
|
||||
</Title>
|
||||
<Table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Table.Tbody>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Trans>Server Version</Trans>
|
||||
</td>
|
||||
<td>{server.version}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>{server.version}</Table.Td>
|
||||
</Table.Tr>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Trans>API Version</Trans>
|
||||
</td>
|
||||
<td>{server.apiVersion}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table.Td>
|
||||
<Table.Td>{server.apiVersion}</Table.Td>
|
||||
</Table.Tr>
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
<Divider />
|
||||
<Group justify="right">
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Flex, FocusTrap, Modal, NumberInput, TextInput } from '@mantine/core';
|
||||
import {
|
||||
Flex,
|
||||
FocusTrap,
|
||||
Modal,
|
||||
NumberInput,
|
||||
Table,
|
||||
TextInput
|
||||
} from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import {
|
||||
IconAddressBook,
|
||||
@ -24,6 +31,7 @@ import {
|
||||
ApiFormAdjustFilterType,
|
||||
ApiFormFieldSet
|
||||
} from '../components/forms/fields/ApiFormField';
|
||||
import { TableFieldExtraRow } from '../components/forms/fields/TableField';
|
||||
import { Thumbnail } from '../components/images/Thumbnail';
|
||||
import { ProgressBar } from '../components/items/ProgressBar';
|
||||
import { StylishText } from '../components/items/StylishText';
|
||||
@ -308,8 +316,8 @@ function LineItemFormRow({
|
||||
/>
|
||||
</FocusTrap>
|
||||
</Modal>
|
||||
<tr>
|
||||
<td>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Flex gap="sm" align="center">
|
||||
<Thumbnail
|
||||
size={40}
|
||||
@ -318,16 +326,16 @@ function LineItemFormRow({
|
||||
/>
|
||||
<div>{record.part_detail.name}</div>
|
||||
</Flex>
|
||||
</td>
|
||||
<td>{record.supplier_part_detail.SKU}</td>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>{record.supplier_part_detail.SKU}</Table.Td>
|
||||
<Table.Td>
|
||||
<ProgressBar
|
||||
value={record.received}
|
||||
maximum={record.quantity}
|
||||
progressLabel
|
||||
/>
|
||||
</td>
|
||||
<td style={{ width: '1%', whiteSpace: 'nowrap' }}>
|
||||
</Table.Td>
|
||||
<Table.Td style={{ width: '1%', whiteSpace: 'nowrap' }}>
|
||||
<NumberInput
|
||||
value={input.item.quantity}
|
||||
style={{ width: '100px' }}
|
||||
@ -335,8 +343,8 @@ function LineItemFormRow({
|
||||
min={0}
|
||||
onChange={(value) => input.changeFn(input.idx, 'quantity', value)}
|
||||
/>
|
||||
</td>
|
||||
<td style={{ width: '1%', whiteSpace: 'nowrap' }}>
|
||||
</Table.Td>
|
||||
<Table.Td style={{ width: '1%', whiteSpace: 'nowrap' }}>
|
||||
<Flex gap="1px">
|
||||
<ActionButton
|
||||
onClick={() => locationHandlers.toggle()}
|
||||
@ -387,11 +395,11 @@ function LineItemFormRow({
|
||||
color="red"
|
||||
/>
|
||||
</Flex>
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
{locationOpen && (
|
||||
<tr>
|
||||
<td colSpan={4}>
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={4}>
|
||||
<Flex align="end" gap={5}>
|
||||
<div style={{ flexGrow: '1' }}>
|
||||
<StandaloneField
|
||||
@ -453,8 +461,8 @@ function LineItemFormRow({
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</td>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
@ -466,107 +474,54 @@ function LineItemFormRow({
|
||||
>
|
||||
<InvenTreeIcon icon="downleft" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{batchOpen && (
|
||||
<>
|
||||
<tr>
|
||||
<td colSpan={4}>
|
||||
<Flex align="end" gap={5}>
|
||||
<div style={{ flexGrow: '1' }}>
|
||||
<StandaloneField
|
||||
fieldDefinition={{
|
||||
field_type: 'string',
|
||||
onValueChange: (value) => setBatchCode(value),
|
||||
label: 'Batch Code',
|
||||
value: batchCode
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Flex>
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(6, 1fr)',
|
||||
gridTemplateRows: 'auto',
|
||||
alignItems: 'end'
|
||||
}}
|
||||
>
|
||||
<span></span>
|
||||
<InvenTreeIcon icon="downleft" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{record.trackable && (
|
||||
<tr>
|
||||
<td colSpan={4}>
|
||||
<Flex align="end" gap={5}>
|
||||
<div style={{ flexGrow: '1' }}>
|
||||
<StandaloneField
|
||||
fieldDefinition={{
|
||||
field_type: 'string',
|
||||
onValueChange: (value) => setSerials(value),
|
||||
label: 'Serial numbers',
|
||||
value: serials
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Flex>
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(6, 1fr)',
|
||||
gridTemplateRows: 'auto',
|
||||
alignItems: 'end'
|
||||
}}
|
||||
>
|
||||
<span></span>
|
||||
<InvenTreeIcon icon="downleft" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{statusOpen && (
|
||||
<tr>
|
||||
<td colSpan={4}>
|
||||
<StandaloneField
|
||||
fieldDefinition={{
|
||||
field_type: 'choice',
|
||||
api_url: apiUrl(ApiEndpoints.stock_status),
|
||||
choices: statuses,
|
||||
label: 'Status',
|
||||
onValueChange: (value) =>
|
||||
input.changeFn(input.idx, 'status', value)
|
||||
}}
|
||||
defaultValue={10}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(6, 1fr)',
|
||||
gridTemplateRows: 'auto',
|
||||
alignItems: 'end'
|
||||
}}
|
||||
>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<InvenTreeIcon icon="downleft" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
)}
|
||||
<TableFieldExtraRow
|
||||
visible={batchOpen}
|
||||
colSpan={4}
|
||||
content={
|
||||
<StandaloneField
|
||||
fieldDefinition={{
|
||||
field_type: 'string',
|
||||
onValueChange: (value) => setBatchCode(value),
|
||||
label: 'Batch Code',
|
||||
value: batchCode
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<TableFieldExtraRow
|
||||
visible={batchOpen && record.trackable}
|
||||
colSpan={4}
|
||||
content={
|
||||
<StandaloneField
|
||||
fieldDefinition={{
|
||||
field_type: 'string',
|
||||
onValueChange: (value) => setSerials(value),
|
||||
label: 'Serial numbers',
|
||||
value: serials
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<TableFieldExtraRow
|
||||
visible={statusOpen}
|
||||
colSpan={4}
|
||||
content={
|
||||
<StandaloneField
|
||||
fieldDefinition={{
|
||||
field_type: 'choice',
|
||||
api_url: apiUrl(ApiEndpoints.stock_status),
|
||||
choices: statuses,
|
||||
label: 'Status',
|
||||
onValueChange: (value) =>
|
||||
input.changeFn(input.idx, 'status', value)
|
||||
}}
|
||||
defaultValue={10}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -655,11 +610,11 @@ export function useReceiveLineItems(props: LineItemsForm) {
|
||||
return useCreateApiFormModal({
|
||||
...props.formProps,
|
||||
url: url,
|
||||
title: t`Receive line items`,
|
||||
title: t`Receive Line Items`,
|
||||
fields: fields,
|
||||
initialData: {
|
||||
location: null
|
||||
},
|
||||
size: 'max(60%,800px)'
|
||||
size: 'xl'
|
||||
});
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Flex, Group, NumberInput, Skeleton, Text } from '@mantine/core';
|
||||
import { Flex, Group, NumberInput, Skeleton, Table, Text } from '@mantine/core';
|
||||
import { modals } from '@mantine/modals';
|
||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { Suspense, useCallback, useMemo, useState } from 'react';
|
||||
@ -297,8 +297,8 @@ function StockOperationsRow({
|
||||
return !record ? (
|
||||
<div>{t`Loading...`}</div>
|
||||
) : (
|
||||
<tr>
|
||||
<td>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Flex gap="sm" align="center">
|
||||
<Thumbnail
|
||||
size={40}
|
||||
@ -307,18 +307,20 @@ function StockOperationsRow({
|
||||
/>
|
||||
<div>{record.part_detail?.name}</div>
|
||||
</Flex>
|
||||
</td>
|
||||
<td>{record.location ? record.location_detail?.pathstring : '-'}</td>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
{record.location ? record.location_detail?.pathstring : '-'}
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Flex align="center" gap="xs">
|
||||
<Group justify="space-between">
|
||||
<Text>{stockString}</Text>
|
||||
<StatusRenderer status={record.status} type={ModelType.stockitem} />
|
||||
</Group>
|
||||
</Flex>
|
||||
</td>
|
||||
</Table.Td>
|
||||
{!merge && (
|
||||
<td>
|
||||
<Table.Td>
|
||||
<NumberInput
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
@ -327,9 +329,9 @@ function StockOperationsRow({
|
||||
min={0}
|
||||
style={{ maxWidth: '100px' }}
|
||||
/>
|
||||
</td>
|
||||
</Table.Td>
|
||||
)}
|
||||
<td>
|
||||
<Table.Td>
|
||||
<Flex gap="3px">
|
||||
{transfer && (
|
||||
<ActionButton
|
||||
@ -351,8 +353,8 @@ function StockOperationsRow({
|
||||
color="red"
|
||||
/>
|
||||
</Flex>
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
IconClipboardText,
|
||||
IconCopy,
|
||||
IconCornerDownLeft,
|
||||
IconCornerDownRight,
|
||||
IconCornerUpRightDouble,
|
||||
IconCurrencyDollar,
|
||||
IconDots,
|
||||
@ -190,6 +191,7 @@ const icons = {
|
||||
phone: IconPhone,
|
||||
sitemap: IconSitemap,
|
||||
downleft: IconCornerDownLeft,
|
||||
downright: IconCornerDownRight,
|
||||
barcode: IconQrcode,
|
||||
barLine: IconMinusVertical,
|
||||
batch_code: IconClipboardText,
|
||||
|
@ -81,41 +81,41 @@ export function UserTheme({ height }: { height: number }) {
|
||||
<Trans>Theme</Trans>
|
||||
</Title>
|
||||
<Table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Table.Tbody>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Trans>Primary color</Trans>
|
||||
</td>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<ColorPicker
|
||||
format="hex"
|
||||
onChange={changePrimary}
|
||||
withPicker={false}
|
||||
swatches={Object.keys(LOOKUP)}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Trans>White color</Trans>
|
||||
</td>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<ColorInput value={whiteColor} onChange={changeWhite} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Trans>Black color</Trans>
|
||||
</td>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<ColorInput value={blackColor} onChange={changeBlack} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Trans>Border Radius</Trans>
|
||||
</td>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Slider
|
||||
label={(val) => getMark(val).label}
|
||||
defaultValue={50}
|
||||
@ -125,13 +125,13 @@ export function UserTheme({ height }: { height: number }) {
|
||||
onChange={changeRadius}
|
||||
mb={18}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<Trans>Loader</Trans>
|
||||
</td>
|
||||
<td>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Group align="center">
|
||||
<Select
|
||||
data={loaderDate}
|
||||
@ -140,9 +140,9 @@ export function UserTheme({ height }: { height: number }) {
|
||||
/>
|
||||
<Loader type={themeLoader} mah={18} />
|
||||
</Group>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Container>
|
||||
);
|
||||
|
@ -8,12 +8,13 @@ import {
|
||||
IconSitemap
|
||||
} from '@tabler/icons-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
import {
|
||||
ActionDropdown,
|
||||
DeleteItemAction,
|
||||
EditItemAction
|
||||
} from '../../components/items/ActionDropdown';
|
||||
import { PageDetail } from '../../components/nav/PageDetail';
|
||||
@ -24,7 +25,10 @@ import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { partCategoryFields } from '../../forms/PartForms';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
||||
import {
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import ParametricPartTable from '../../tables/part/ParametricPartTable';
|
||||
@ -43,6 +47,7 @@ export default function CategoryDetail({}: {}) {
|
||||
[_id]
|
||||
);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const user = useUserState();
|
||||
|
||||
const [treeOpen, setTreeOpen] = useState(false);
|
||||
@ -154,6 +159,46 @@ export default function CategoryDetail({}: {}) {
|
||||
onFormSuccess: refreshInstance
|
||||
});
|
||||
|
||||
const deleteOptions = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
value: 0,
|
||||
display_name: `Move items to parent category`
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
display_name: t`Delete items`
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
const deleteCategory = useDeleteApiFormModal({
|
||||
url: ApiEndpoints.category_list,
|
||||
pk: id,
|
||||
title: t`Delete Part Category`,
|
||||
fields: {
|
||||
delete_parts: {
|
||||
label: t`Parts Action`,
|
||||
description: t`Action for parts in this category`,
|
||||
choices: deleteOptions,
|
||||
field_type: 'choice'
|
||||
},
|
||||
delete_child_categories: {
|
||||
label: t`Child Categories Action`,
|
||||
description: t`Action for child categories in this category`,
|
||||
choices: deleteOptions,
|
||||
field_type: 'choice'
|
||||
}
|
||||
},
|
||||
onFormSuccess: () => {
|
||||
if (category.parent) {
|
||||
navigate(getDetailUrl(ModelType.partcategory, category.parent));
|
||||
} else {
|
||||
navigate('/part/');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const categoryActions = useMemo(() => {
|
||||
return [
|
||||
<ActionDropdown
|
||||
@ -165,6 +210,11 @@ export default function CategoryDetail({}: {}) {
|
||||
hidden: !id || !user.hasChangeRole(UserRoles.part_category),
|
||||
tooltip: t`Edit Part Category`,
|
||||
onClick: () => editCategory.open()
|
||||
}),
|
||||
DeleteItemAction({
|
||||
hidden: !id || !user.hasDeleteRole(UserRoles.part_category),
|
||||
tooltip: t`Delete Part Category`,
|
||||
onClick: () => deleteCategory.open()
|
||||
})
|
||||
]}
|
||||
/>
|
||||
@ -223,6 +273,7 @@ export default function CategoryDetail({}: {}) {
|
||||
return (
|
||||
<>
|
||||
{editCategory.modal}
|
||||
{deleteCategory.modal}
|
||||
<Stack gap="xs">
|
||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||
<PartCategoryTree
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
|
||||
import {
|
||||
Alert,
|
||||
Grid,
|
||||
LoadingOverlay,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table
|
||||
} from '@mantine/core';
|
||||
import {
|
||||
IconBookmarks,
|
||||
IconBuilding,
|
||||
@ -24,7 +31,7 @@ import {
|
||||
} from '@tabler/icons-react';
|
||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { ReactNode, useMemo, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { api } from '../../App';
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
@ -32,6 +39,7 @@ import DetailsBadge from '../../components/details/DetailsBadge';
|
||||
import { DetailsImage } from '../../components/details/DetailsImage';
|
||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
import { PartIcons } from '../../components/details/PartIcons';
|
||||
import { Thumbnail } from '../../components/images/Thumbnail';
|
||||
import {
|
||||
ActionDropdown,
|
||||
BarcodeActionDropdown,
|
||||
@ -60,6 +68,7 @@ import { InvenTreeIcon } from '../../functions/icons';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
@ -85,6 +94,7 @@ import PartPricingPanel from './PartPricingPanel';
|
||||
export default function PartDetail() {
|
||||
const { id } = useParams();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const user = useUserState();
|
||||
|
||||
const [treeOpen, setTreeOpen] = useState(false);
|
||||
@ -443,13 +453,13 @@ export default function PartDetail() {
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<Stack gap="xs">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<Table>
|
||||
<Table.Tbody>
|
||||
<Table.Tr>
|
||||
<PartIcons part={part} />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Table.Tr>
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
<DetailsTable fields={tl} item={part} />
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
@ -700,6 +710,26 @@ export default function PartDetail() {
|
||||
modelType: ModelType.part
|
||||
});
|
||||
|
||||
const deletePart = useDeleteApiFormModal({
|
||||
url: ApiEndpoints.part_list,
|
||||
pk: part.pk,
|
||||
title: t`Delete Part`,
|
||||
onFormSuccess: () => {
|
||||
if (part.category) {
|
||||
navigate(getDetailUrl(ModelType.partcategory, part.category));
|
||||
} else {
|
||||
navigate('/part/');
|
||||
}
|
||||
},
|
||||
preFormContent: (
|
||||
<Alert color="red" title={t`Deleting this part cannot be reversed`}>
|
||||
<Stack gap="xs">
|
||||
<Thumbnail src={part.thumbnail ?? part.image} text={part.full_name} />
|
||||
</Stack>
|
||||
</Alert>
|
||||
)
|
||||
});
|
||||
|
||||
const stockActionProps: StockOperationProps = useMemo(() => {
|
||||
return {
|
||||
pk: part.pk,
|
||||
@ -771,7 +801,9 @@ export default function PartDetail() {
|
||||
onClick: () => editPart.open()
|
||||
}),
|
||||
DeleteItemAction({
|
||||
hidden: part?.active || !user.hasDeleteRole(UserRoles.part)
|
||||
hidden: !user.hasDeleteRole(UserRoles.part),
|
||||
disabled: part.active,
|
||||
onClick: () => deletePart.open()
|
||||
})
|
||||
]}
|
||||
/>
|
||||
@ -782,6 +814,7 @@ export default function PartDetail() {
|
||||
<>
|
||||
{duplicatePart.modal}
|
||||
{editPart.modal}
|
||||
{deletePart.modal}
|
||||
<Stack gap="xs">
|
||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||
<PartCategoryTree
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
IconSitemap
|
||||
} from '@tabler/icons-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { ActionButton } from '../../components/buttons/ActionButton';
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
@ -15,6 +15,7 @@ import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||
import {
|
||||
ActionDropdown,
|
||||
BarcodeActionDropdown,
|
||||
DeleteItemAction,
|
||||
EditItemAction,
|
||||
LinkBarcodeAction,
|
||||
UnlinkBarcodeAction,
|
||||
@ -34,7 +35,10 @@ import {
|
||||
} from '../../forms/StockForms';
|
||||
import { InvenTreeIcon } from '../../functions/icons';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
||||
import {
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { PartListTable } from '../../tables/part/PartTable';
|
||||
@ -49,6 +53,7 @@ export default function Stock() {
|
||||
[_id]
|
||||
);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const user = useUserState();
|
||||
|
||||
const [treeOpen, setTreeOpen] = useState(false);
|
||||
@ -197,6 +202,46 @@ export default function Stock() {
|
||||
onFormSuccess: refreshInstance
|
||||
});
|
||||
|
||||
const deleteOptions = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
value: 0,
|
||||
display_name: `Move items to parent location`
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
display_name: t`Delete items`
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
const deleteLocation = useDeleteApiFormModal({
|
||||
url: ApiEndpoints.stock_location_list,
|
||||
pk: id,
|
||||
title: t`Delete Stock Location`,
|
||||
fields: {
|
||||
delete_stock_items: {
|
||||
label: t`Items Action`,
|
||||
description: t`Action for stock items in this location`,
|
||||
field_type: 'choice',
|
||||
choices: deleteOptions
|
||||
},
|
||||
delete_sub_location: {
|
||||
label: t`Child Locations Action`,
|
||||
description: t`Action for child locations in this location`,
|
||||
field_type: 'choice',
|
||||
choices: deleteOptions
|
||||
}
|
||||
},
|
||||
onFormSuccess: () => {
|
||||
if (location.parent) {
|
||||
navigate(getDetailUrl(ModelType.stocklocation, location.parent));
|
||||
} else {
|
||||
navigate('/stock/');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const stockItemActionProps: StockOperationProps = useMemo(() => {
|
||||
return {
|
||||
pk: location.pk,
|
||||
@ -282,6 +327,11 @@ export default function Stock() {
|
||||
hidden: !id || !user.hasChangeRole(UserRoles.stock_location),
|
||||
tooltip: t`Edit Stock Location`,
|
||||
onClick: () => editLocation.open()
|
||||
}),
|
||||
DeleteItemAction({
|
||||
hidden: !id || !user.hasDeleteRole(UserRoles.stock_location),
|
||||
tooltip: t`Delete Stock Location`,
|
||||
onClick: () => deleteLocation.open()
|
||||
})
|
||||
]}
|
||||
/>
|
||||
@ -303,6 +353,7 @@ export default function Stock() {
|
||||
return (
|
||||
<>
|
||||
{editLocation.modal}
|
||||
{deleteLocation.modal}
|
||||
<Stack>
|
||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||
<StockLocationTree
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
IconSitemap
|
||||
} from '@tabler/icons-react';
|
||||
import { ReactNode, useMemo, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||
import DetailsBadge from '../../components/details/DetailsBadge';
|
||||
@ -49,6 +49,7 @@ import { InvenTreeIcon } from '../../functions/icons';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
@ -64,6 +65,8 @@ export default function StockDetail() {
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [treeOpen, setTreeOpen] = useState(false);
|
||||
|
||||
const {
|
||||
@ -370,6 +373,23 @@ export default function StockDetail() {
|
||||
modelType: ModelType.stockitem
|
||||
});
|
||||
|
||||
const preDeleteContent = useMemo(() => {
|
||||
// TODO: Fill this out with information on the stock item.
|
||||
// e.g. list of child items which would be deleted, etc
|
||||
return undefined;
|
||||
}, [stockitem]);
|
||||
|
||||
const deleteStockItem = useDeleteApiFormModal({
|
||||
url: ApiEndpoints.stock_item_list,
|
||||
pk: stockitem.pk,
|
||||
title: t`Delete Stock Item`,
|
||||
preFormContent: preDeleteContent,
|
||||
onFormSuccess: () => {
|
||||
// Redirect to the part page
|
||||
navigate(getDetailUrl(ModelType.part, stockitem.part));
|
||||
}
|
||||
});
|
||||
|
||||
const stockActionProps: StockOperationProps = useMemo(() => {
|
||||
return {
|
||||
items: stockitem,
|
||||
@ -458,7 +478,8 @@ export default function StockDetail() {
|
||||
onClick: () => editStockItem.open()
|
||||
}),
|
||||
DeleteItemAction({
|
||||
hidden: !user.hasDeleteRole(UserRoles.stock)
|
||||
hidden: !user.hasDeleteRole(UserRoles.stock),
|
||||
onClick: () => deleteStockItem.open()
|
||||
})
|
||||
]}
|
||||
/>
|
||||
@ -524,6 +545,7 @@ export default function StockDetail() {
|
||||
<PanelGroup pageKey="stockitem" panels={stockPanels} />
|
||||
{editStockItem.modal}
|
||||
{duplicateStockItem.modal}
|
||||
{deleteStockItem.modal}
|
||||
{countStockItem.modal}
|
||||
{addStockItem.modal}
|
||||
{removeStockItem.modal}
|
||||
|
Loading…
Reference in New Issue
Block a user