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 choices.map((choice) => {
|
||||||
return {
|
return {
|
||||||
value: choice.value.toString(),
|
value: choice.value.toString(),
|
||||||
label: choice.display_name.toString()
|
label: choice.display_name ?? choice.value
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [definition.choices]);
|
}, [definition.choices]);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Trans, t } from '@lingui/macro';
|
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 { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||||
|
|
||||||
import { InvenTreeIcon } from '../../../functions/icons';
|
import { InvenTreeIcon } from '../../../functions/icons';
|
||||||
@ -34,19 +34,21 @@ export function TableField({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Table highlightOnHover striped>
|
<Table highlightOnHover striped>
|
||||||
<thead>
|
<Table.Thead>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
{definition.headers?.map((header) => {
|
{definition.headers?.map((header) => {
|
||||||
return <th key={header}>{header}</th>;
|
return <th key={header}>{header}</th>;
|
||||||
})}
|
})}
|
||||||
</tr>
|
</Table.Tr>
|
||||||
</thead>
|
</Table.Thead>
|
||||||
<tbody>
|
<Table.Tbody>
|
||||||
{value.length > 0 ? (
|
{value.length > 0 ? (
|
||||||
value.map((item: any, idx: number) => {
|
value.map((item: any, idx: number) => {
|
||||||
// Table fields require render function
|
// Table fields require render function
|
||||||
if (!definition.modelRenderer) {
|
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({
|
return definition.modelRenderer({
|
||||||
item: item,
|
item: item,
|
||||||
@ -56,8 +58,8 @@ export function TableField({
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td
|
<Table.Td
|
||||||
style={{ textAlign: 'center' }}
|
style={{ textAlign: 'center' }}
|
||||||
colSpan={definition.headers?.length}
|
colSpan={definition.headers?.length}
|
||||||
>
|
>
|
||||||
@ -71,10 +73,36 @@ export function TableField({
|
|||||||
<InvenTreeIcon icon="info" />
|
<InvenTreeIcon icon="info" />
|
||||||
<Trans>No entries available</Trans>
|
<Trans>No entries available</Trans>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</Table.Tbody>
|
||||||
</Table>
|
</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
|
// Common action button for deleting an item
|
||||||
export function DeleteItemAction({
|
export function DeleteItemAction({
|
||||||
hidden = false,
|
hidden = false,
|
||||||
|
disabled = false,
|
||||||
tooltip,
|
tooltip,
|
||||||
onClick
|
onClick
|
||||||
}: {
|
}: {
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}): ActionDropdownItem {
|
}): ActionDropdownItem {
|
||||||
@ -201,7 +203,8 @@ export function DeleteItemAction({
|
|||||||
name: t`Delete`,
|
name: t`Delete`,
|
||||||
tooltip: tooltip ?? t`Delete item`,
|
tooltip: tooltip ?? t`Delete item`,
|
||||||
onClick: onClick,
|
onClick: onClick,
|
||||||
hidden: hidden
|
hidden: hidden,
|
||||||
|
disabled: disabled
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,46 +26,46 @@ export function ServerInfoModal({
|
|||||||
<Trans>Server</Trans>
|
<Trans>Server</Trans>
|
||||||
</Title>
|
</Title>
|
||||||
<Table>
|
<Table>
|
||||||
<tbody>
|
<Table.Tbody>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Instance Name</Trans>
|
<Trans>Instance Name</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>{server.instance}</td>
|
<Table.Td>{server.instance}</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Database</Trans>
|
<Trans>Database</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<OnlyStaff>{server.database}</OnlyStaff>
|
<OnlyStaff>{server.database}</OnlyStaff>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
{server.debug_mode && (
|
{server.debug_mode && (
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Debug Mode</Trans>
|
<Trans>Debug Mode</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Server is running in debug mode</Trans>
|
<Trans>Server is running in debug mode</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
)}
|
)}
|
||||||
{server.docker_mode && (
|
{server.docker_mode && (
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Docker Mode</Trans>
|
<Trans>Docker Mode</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Server is deployed using docker</Trans>
|
<Trans>Server is deployed using docker</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
)}
|
)}
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Plugin Support</Trans>
|
<Trans>Plugin Support</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Badge color={server.plugins_enabled ? 'green' : 'red'}>
|
<Badge color={server.plugins_enabled ? 'green' : 'red'}>
|
||||||
{server.plugins_enabled ? (
|
{server.plugins_enabled ? (
|
||||||
<Trans>Plugin support enabled</Trans>
|
<Trans>Plugin support enabled</Trans>
|
||||||
@ -73,13 +73,13 @@ export function ServerInfoModal({
|
|||||||
<Trans>Plugin support disabled</Trans>
|
<Trans>Plugin support disabled</Trans>
|
||||||
)}
|
)}
|
||||||
</Badge>
|
</Badge>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Server status</Trans>
|
<Trans>Server status</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<OnlyStaff>
|
<OnlyStaff>
|
||||||
<Badge color={server.system_health ? 'green' : 'yellow'}>
|
<Badge color={server.system_health ? 'green' : 'yellow'}>
|
||||||
{server.system_health ? (
|
{server.system_health ? (
|
||||||
@ -89,52 +89,52 @@ export function ServerInfoModal({
|
|||||||
)}
|
)}
|
||||||
</Badge>
|
</Badge>
|
||||||
</OnlyStaff>
|
</OnlyStaff>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
{server.worker_running != true && (
|
{server.worker_running != true && (
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Background Worker</Trans>
|
<Trans>Background Worker</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Badge color="red">
|
<Badge color="red">
|
||||||
<Trans>Background worker not running</Trans>
|
<Trans>Background worker not running</Trans>
|
||||||
</Badge>
|
</Badge>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
)}
|
)}
|
||||||
{server.email_configured != true && (
|
{server.email_configured != true && (
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Email Settings</Trans>
|
<Trans>Email Settings</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Badge color="red">
|
<Badge color="red">
|
||||||
<Trans>Email settings not configured</Trans>
|
<Trans>Email settings not configured</Trans>
|
||||||
</Badge>
|
</Badge>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</Table.Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
<Title order={5}>
|
<Title order={5}>
|
||||||
<Trans>Version</Trans>
|
<Trans>Version</Trans>
|
||||||
</Title>
|
</Title>
|
||||||
<Table>
|
<Table>
|
||||||
<tbody>
|
<Table.Tbody>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Server Version</Trans>
|
<Trans>Server Version</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>{server.version}</td>
|
<Table.Td>{server.version}</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>API Version</Trans>
|
<Trans>API Version</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>{server.apiVersion}</td>
|
<Table.Td>{server.apiVersion}</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
</tbody>
|
</Table.Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Group justify="right">
|
<Group justify="right">
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
import { t } from '@lingui/macro';
|
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 { useDisclosure } from '@mantine/hooks';
|
||||||
import {
|
import {
|
||||||
IconAddressBook,
|
IconAddressBook,
|
||||||
@ -24,6 +31,7 @@ import {
|
|||||||
ApiFormAdjustFilterType,
|
ApiFormAdjustFilterType,
|
||||||
ApiFormFieldSet
|
ApiFormFieldSet
|
||||||
} from '../components/forms/fields/ApiFormField';
|
} from '../components/forms/fields/ApiFormField';
|
||||||
|
import { TableFieldExtraRow } from '../components/forms/fields/TableField';
|
||||||
import { Thumbnail } from '../components/images/Thumbnail';
|
import { Thumbnail } from '../components/images/Thumbnail';
|
||||||
import { ProgressBar } from '../components/items/ProgressBar';
|
import { ProgressBar } from '../components/items/ProgressBar';
|
||||||
import { StylishText } from '../components/items/StylishText';
|
import { StylishText } from '../components/items/StylishText';
|
||||||
@ -308,8 +316,8 @@ function LineItemFormRow({
|
|||||||
/>
|
/>
|
||||||
</FocusTrap>
|
</FocusTrap>
|
||||||
</Modal>
|
</Modal>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Flex gap="sm" align="center">
|
<Flex gap="sm" align="center">
|
||||||
<Thumbnail
|
<Thumbnail
|
||||||
size={40}
|
size={40}
|
||||||
@ -318,16 +326,16 @@ function LineItemFormRow({
|
|||||||
/>
|
/>
|
||||||
<div>{record.part_detail.name}</div>
|
<div>{record.part_detail.name}</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>{record.supplier_part_detail.SKU}</td>
|
<Table.Td>{record.supplier_part_detail.SKU}</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
value={record.received}
|
value={record.received}
|
||||||
maximum={record.quantity}
|
maximum={record.quantity}
|
||||||
progressLabel
|
progressLabel
|
||||||
/>
|
/>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td style={{ width: '1%', whiteSpace: 'nowrap' }}>
|
<Table.Td style={{ width: '1%', whiteSpace: 'nowrap' }}>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
value={input.item.quantity}
|
value={input.item.quantity}
|
||||||
style={{ width: '100px' }}
|
style={{ width: '100px' }}
|
||||||
@ -335,8 +343,8 @@ function LineItemFormRow({
|
|||||||
min={0}
|
min={0}
|
||||||
onChange={(value) => input.changeFn(input.idx, 'quantity', value)}
|
onChange={(value) => input.changeFn(input.idx, 'quantity', value)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td style={{ width: '1%', whiteSpace: 'nowrap' }}>
|
<Table.Td style={{ width: '1%', whiteSpace: 'nowrap' }}>
|
||||||
<Flex gap="1px">
|
<Flex gap="1px">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
onClick={() => locationHandlers.toggle()}
|
onClick={() => locationHandlers.toggle()}
|
||||||
@ -387,11 +395,11 @@ function LineItemFormRow({
|
|||||||
color="red"
|
color="red"
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
{locationOpen && (
|
{locationOpen && (
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td colSpan={4}>
|
<Table.Td colSpan={4}>
|
||||||
<Flex align="end" gap={5}>
|
<Flex align="end" gap={5}>
|
||||||
<div style={{ flexGrow: '1' }}>
|
<div style={{ flexGrow: '1' }}>
|
||||||
<StandaloneField
|
<StandaloneField
|
||||||
@ -453,8 +461,8 @@ function LineItemFormRow({
|
|||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@ -466,107 +474,54 @@ function LineItemFormRow({
|
|||||||
>
|
>
|
||||||
<InvenTreeIcon icon="downleft" />
|
<InvenTreeIcon icon="downleft" />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.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>
|
|
||||||
)}
|
)}
|
||||||
|
<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({
|
return useCreateApiFormModal({
|
||||||
...props.formProps,
|
...props.formProps,
|
||||||
url: url,
|
url: url,
|
||||||
title: t`Receive line items`,
|
title: t`Receive Line Items`,
|
||||||
fields: fields,
|
fields: fields,
|
||||||
initialData: {
|
initialData: {
|
||||||
location: null
|
location: null
|
||||||
},
|
},
|
||||||
size: 'max(60%,800px)'
|
size: 'xl'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
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 { modals } from '@mantine/modals';
|
||||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||||
import { Suspense, useCallback, useMemo, useState } from 'react';
|
import { Suspense, useCallback, useMemo, useState } from 'react';
|
||||||
@ -297,8 +297,8 @@ function StockOperationsRow({
|
|||||||
return !record ? (
|
return !record ? (
|
||||||
<div>{t`Loading...`}</div>
|
<div>{t`Loading...`}</div>
|
||||||
) : (
|
) : (
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Flex gap="sm" align="center">
|
<Flex gap="sm" align="center">
|
||||||
<Thumbnail
|
<Thumbnail
|
||||||
size={40}
|
size={40}
|
||||||
@ -307,18 +307,20 @@ function StockOperationsRow({
|
|||||||
/>
|
/>
|
||||||
<div>{record.part_detail?.name}</div>
|
<div>{record.part_detail?.name}</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>{record.location ? record.location_detail?.pathstring : '-'}</td>
|
<Table.Td>
|
||||||
<td>
|
{record.location ? record.location_detail?.pathstring : '-'}
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
<Flex align="center" gap="xs">
|
<Flex align="center" gap="xs">
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Text>{stockString}</Text>
|
<Text>{stockString}</Text>
|
||||||
<StatusRenderer status={record.status} type={ModelType.stockitem} />
|
<StatusRenderer status={record.status} type={ModelType.stockitem} />
|
||||||
</Group>
|
</Group>
|
||||||
</Flex>
|
</Flex>
|
||||||
</td>
|
</Table.Td>
|
||||||
{!merge && (
|
{!merge && (
|
||||||
<td>
|
<Table.Td>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
@ -327,9 +329,9 @@ function StockOperationsRow({
|
|||||||
min={0}
|
min={0}
|
||||||
style={{ maxWidth: '100px' }}
|
style={{ maxWidth: '100px' }}
|
||||||
/>
|
/>
|
||||||
</td>
|
</Table.Td>
|
||||||
)}
|
)}
|
||||||
<td>
|
<Table.Td>
|
||||||
<Flex gap="3px">
|
<Flex gap="3px">
|
||||||
{transfer && (
|
{transfer && (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
@ -351,8 +353,8 @@ function StockOperationsRow({
|
|||||||
color="red"
|
color="red"
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
IconClipboardText,
|
IconClipboardText,
|
||||||
IconCopy,
|
IconCopy,
|
||||||
IconCornerDownLeft,
|
IconCornerDownLeft,
|
||||||
|
IconCornerDownRight,
|
||||||
IconCornerUpRightDouble,
|
IconCornerUpRightDouble,
|
||||||
IconCurrencyDollar,
|
IconCurrencyDollar,
|
||||||
IconDots,
|
IconDots,
|
||||||
@ -190,6 +191,7 @@ const icons = {
|
|||||||
phone: IconPhone,
|
phone: IconPhone,
|
||||||
sitemap: IconSitemap,
|
sitemap: IconSitemap,
|
||||||
downleft: IconCornerDownLeft,
|
downleft: IconCornerDownLeft,
|
||||||
|
downright: IconCornerDownRight,
|
||||||
barcode: IconQrcode,
|
barcode: IconQrcode,
|
||||||
barLine: IconMinusVertical,
|
barLine: IconMinusVertical,
|
||||||
batch_code: IconClipboardText,
|
batch_code: IconClipboardText,
|
||||||
|
@ -81,41 +81,41 @@ export function UserTheme({ height }: { height: number }) {
|
|||||||
<Trans>Theme</Trans>
|
<Trans>Theme</Trans>
|
||||||
</Title>
|
</Title>
|
||||||
<Table>
|
<Table>
|
||||||
<tbody>
|
<Table.Tbody>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Primary color</Trans>
|
<Trans>Primary color</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
format="hex"
|
format="hex"
|
||||||
onChange={changePrimary}
|
onChange={changePrimary}
|
||||||
withPicker={false}
|
withPicker={false}
|
||||||
swatches={Object.keys(LOOKUP)}
|
swatches={Object.keys(LOOKUP)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>White color</Trans>
|
<Trans>White color</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<ColorInput value={whiteColor} onChange={changeWhite} />
|
<ColorInput value={whiteColor} onChange={changeWhite} />
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Black color</Trans>
|
<Trans>Black color</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<ColorInput value={blackColor} onChange={changeBlack} />
|
<ColorInput value={blackColor} onChange={changeBlack} />
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Border Radius</Trans>
|
<Trans>Border Radius</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Slider
|
<Slider
|
||||||
label={(val) => getMark(val).label}
|
label={(val) => getMark(val).label}
|
||||||
defaultValue={50}
|
defaultValue={50}
|
||||||
@ -125,13 +125,13 @@ export function UserTheme({ height }: { height: number }) {
|
|||||||
onChange={changeRadius}
|
onChange={changeRadius}
|
||||||
mb={18}
|
mb={18}
|
||||||
/>
|
/>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Loader</Trans>
|
<Trans>Loader</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Group align="center">
|
<Group align="center">
|
||||||
<Select
|
<Select
|
||||||
data={loaderDate}
|
data={loaderDate}
|
||||||
@ -140,9 +140,9 @@ export function UserTheme({ height }: { height: number }) {
|
|||||||
/>
|
/>
|
||||||
<Loader type={themeLoader} mah={18} />
|
<Loader type={themeLoader} mah={18} />
|
||||||
</Group>
|
</Group>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
</tbody>
|
</Table.Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
@ -8,12 +8,13 @@ import {
|
|||||||
IconSitemap
|
IconSitemap
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useMemo, useState } from '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 { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||||
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
||||||
import {
|
import {
|
||||||
ActionDropdown,
|
ActionDropdown,
|
||||||
|
DeleteItemAction,
|
||||||
EditItemAction
|
EditItemAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
@ -24,7 +25,10 @@ import { ModelType } from '../../enums/ModelType';
|
|||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { partCategoryFields } from '../../forms/PartForms';
|
import { partCategoryFields } from '../../forms/PartForms';
|
||||||
import { getDetailUrl } from '../../functions/urls';
|
import { getDetailUrl } from '../../functions/urls';
|
||||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
import {
|
||||||
|
useDeleteApiFormModal,
|
||||||
|
useEditApiFormModal
|
||||||
|
} from '../../hooks/UseForm';
|
||||||
import { useInstance } from '../../hooks/UseInstance';
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import ParametricPartTable from '../../tables/part/ParametricPartTable';
|
import ParametricPartTable from '../../tables/part/ParametricPartTable';
|
||||||
@ -43,6 +47,7 @@ export default function CategoryDetail({}: {}) {
|
|||||||
[_id]
|
[_id]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
|
||||||
const [treeOpen, setTreeOpen] = useState(false);
|
const [treeOpen, setTreeOpen] = useState(false);
|
||||||
@ -154,6 +159,46 @@ export default function CategoryDetail({}: {}) {
|
|||||||
onFormSuccess: refreshInstance
|
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(() => {
|
const categoryActions = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
@ -165,6 +210,11 @@ export default function CategoryDetail({}: {}) {
|
|||||||
hidden: !id || !user.hasChangeRole(UserRoles.part_category),
|
hidden: !id || !user.hasChangeRole(UserRoles.part_category),
|
||||||
tooltip: t`Edit Part Category`,
|
tooltip: t`Edit Part Category`,
|
||||||
onClick: () => editCategory.open()
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{editCategory.modal}
|
{editCategory.modal}
|
||||||
|
{deleteCategory.modal}
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<PartCategoryTree
|
<PartCategoryTree
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
|
import {
|
||||||
|
Alert,
|
||||||
|
Grid,
|
||||||
|
LoadingOverlay,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table
|
||||||
|
} from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
IconBookmarks,
|
IconBookmarks,
|
||||||
IconBuilding,
|
IconBuilding,
|
||||||
@ -24,7 +31,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 { useParams } from 'react-router-dom';
|
import { useNavigate, 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';
|
||||||
@ -32,6 +39,7 @@ import DetailsBadge from '../../components/details/DetailsBadge';
|
|||||||
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 { PartIcons } from '../../components/details/PartIcons';
|
import { PartIcons } from '../../components/details/PartIcons';
|
||||||
|
import { Thumbnail } from '../../components/images/Thumbnail';
|
||||||
import {
|
import {
|
||||||
ActionDropdown,
|
ActionDropdown,
|
||||||
BarcodeActionDropdown,
|
BarcodeActionDropdown,
|
||||||
@ -60,6 +68,7 @@ import { InvenTreeIcon } from '../../functions/icons';
|
|||||||
import { getDetailUrl } from '../../functions/urls';
|
import { getDetailUrl } from '../../functions/urls';
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
|
useDeleteApiFormModal,
|
||||||
useEditApiFormModal
|
useEditApiFormModal
|
||||||
} from '../../hooks/UseForm';
|
} from '../../hooks/UseForm';
|
||||||
import { useInstance } from '../../hooks/UseInstance';
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
@ -85,6 +94,7 @@ import PartPricingPanel from './PartPricingPanel';
|
|||||||
export default function PartDetail() {
|
export default function PartDetail() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
|
||||||
const [treeOpen, setTreeOpen] = useState(false);
|
const [treeOpen, setTreeOpen] = useState(false);
|
||||||
@ -443,13 +453,13 @@ export default function PartDetail() {
|
|||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={8}>
|
<Grid.Col span={8}>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<table>
|
<Table>
|
||||||
<tbody>
|
<Table.Tbody>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<PartIcons part={part} />
|
<PartIcons part={part} />
|
||||||
</tr>
|
</Table.Tr>
|
||||||
</tbody>
|
</Table.Tbody>
|
||||||
</table>
|
</Table>
|
||||||
<DetailsTable fields={tl} item={part} />
|
<DetailsTable fields={tl} item={part} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
@ -700,6 +710,26 @@ export default function PartDetail() {
|
|||||||
modelType: ModelType.part
|
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(() => {
|
const stockActionProps: StockOperationProps = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
pk: part.pk,
|
pk: part.pk,
|
||||||
@ -771,7 +801,9 @@ export default function PartDetail() {
|
|||||||
onClick: () => editPart.open()
|
onClick: () => editPart.open()
|
||||||
}),
|
}),
|
||||||
DeleteItemAction({
|
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}
|
{duplicatePart.modal}
|
||||||
{editPart.modal}
|
{editPart.modal}
|
||||||
|
{deletePart.modal}
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<PartCategoryTree
|
<PartCategoryTree
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
IconSitemap
|
IconSitemap
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useMemo, useState } from '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 { ActionButton } from '../../components/buttons/ActionButton';
|
||||||
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
import { DetailsField, DetailsTable } from '../../components/details/Details';
|
||||||
@ -15,6 +15,7 @@ import { ItemDetailsGrid } from '../../components/details/ItemDetails';
|
|||||||
import {
|
import {
|
||||||
ActionDropdown,
|
ActionDropdown,
|
||||||
BarcodeActionDropdown,
|
BarcodeActionDropdown,
|
||||||
|
DeleteItemAction,
|
||||||
EditItemAction,
|
EditItemAction,
|
||||||
LinkBarcodeAction,
|
LinkBarcodeAction,
|
||||||
UnlinkBarcodeAction,
|
UnlinkBarcodeAction,
|
||||||
@ -34,7 +35,10 @@ import {
|
|||||||
} from '../../forms/StockForms';
|
} from '../../forms/StockForms';
|
||||||
import { InvenTreeIcon } from '../../functions/icons';
|
import { InvenTreeIcon } from '../../functions/icons';
|
||||||
import { getDetailUrl } from '../../functions/urls';
|
import { getDetailUrl } from '../../functions/urls';
|
||||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
import {
|
||||||
|
useDeleteApiFormModal,
|
||||||
|
useEditApiFormModal
|
||||||
|
} from '../../hooks/UseForm';
|
||||||
import { useInstance } from '../../hooks/UseInstance';
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import { PartListTable } from '../../tables/part/PartTable';
|
import { PartListTable } from '../../tables/part/PartTable';
|
||||||
@ -49,6 +53,7 @@ export default function Stock() {
|
|||||||
[_id]
|
[_id]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
|
||||||
const [treeOpen, setTreeOpen] = useState(false);
|
const [treeOpen, setTreeOpen] = useState(false);
|
||||||
@ -197,6 +202,46 @@ export default function Stock() {
|
|||||||
onFormSuccess: refreshInstance
|
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(() => {
|
const stockItemActionProps: StockOperationProps = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
pk: location.pk,
|
pk: location.pk,
|
||||||
@ -282,6 +327,11 @@ export default function Stock() {
|
|||||||
hidden: !id || !user.hasChangeRole(UserRoles.stock_location),
|
hidden: !id || !user.hasChangeRole(UserRoles.stock_location),
|
||||||
tooltip: t`Edit Stock Location`,
|
tooltip: t`Edit Stock Location`,
|
||||||
onClick: () => editLocation.open()
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{editLocation.modal}
|
{editLocation.modal}
|
||||||
|
{deleteLocation.modal}
|
||||||
<Stack>
|
<Stack>
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
<StockLocationTree
|
<StockLocationTree
|
||||||
|
@ -13,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 { 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 DetailsBadge from '../../components/details/DetailsBadge';
|
import DetailsBadge from '../../components/details/DetailsBadge';
|
||||||
@ -49,6 +49,7 @@ import { InvenTreeIcon } from '../../functions/icons';
|
|||||||
import { getDetailUrl } from '../../functions/urls';
|
import { getDetailUrl } from '../../functions/urls';
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
|
useDeleteApiFormModal,
|
||||||
useEditApiFormModal
|
useEditApiFormModal
|
||||||
} from '../../hooks/UseForm';
|
} from '../../hooks/UseForm';
|
||||||
import { useInstance } from '../../hooks/UseInstance';
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
@ -64,6 +65,8 @@ 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 {
|
||||||
@ -370,6 +373,23 @@ export default function StockDetail() {
|
|||||||
modelType: ModelType.stockitem
|
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(() => {
|
const stockActionProps: StockOperationProps = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
items: stockitem,
|
items: stockitem,
|
||||||
@ -458,7 +478,8 @@ export default function StockDetail() {
|
|||||||
onClick: () => editStockItem.open()
|
onClick: () => editStockItem.open()
|
||||||
}),
|
}),
|
||||||
DeleteItemAction({
|
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} />
|
<PanelGroup pageKey="stockitem" panels={stockPanels} />
|
||||||
{editStockItem.modal}
|
{editStockItem.modal}
|
||||||
{duplicateStockItem.modal}
|
{duplicateStockItem.modal}
|
||||||
|
{deleteStockItem.modal}
|
||||||
{countStockItem.modal}
|
{countStockItem.modal}
|
||||||
{addStockItem.modal}
|
{addStockItem.modal}
|
||||||
{removeStockItem.modal}
|
{removeStockItem.modal}
|
||||||
|
Loading…
Reference in New Issue
Block a user