mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
[PUI] Fix for hovering in parameteric part table (#7933)
* Fix for hovering in parameteric part table - Visual glitches are now gone * Prevent editing of template field
This commit is contained in:
parent
7cbaeb159e
commit
dbe12c2c53
@ -141,7 +141,11 @@ export function partCategoryFields(): ApiFormFieldSet {
|
|||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePartParameterFields(): ApiFormFieldSet {
|
export function usePartParameterFields({
|
||||||
|
editTemplate
|
||||||
|
}: {
|
||||||
|
editTemplate?: boolean;
|
||||||
|
}): ApiFormFieldSet {
|
||||||
// Valid field choices
|
// Valid field choices
|
||||||
const [choices, setChoices] = useState<any[]>([]);
|
const [choices, setChoices] = useState<any[]>([]);
|
||||||
|
|
||||||
@ -156,6 +160,7 @@ export function usePartParameterFields(): ApiFormFieldSet {
|
|||||||
disabled: true
|
disabled: true
|
||||||
},
|
},
|
||||||
template: {
|
template: {
|
||||||
|
disabled: editTemplate == false,
|
||||||
onValueChange: (value: any, record: any) => {
|
onValueChange: (value: any, record: any) => {
|
||||||
// Adjust the type of the "data" field based on the selected template
|
// Adjust the type of the "data" field based on the selected template
|
||||||
if (record?.checkbox) {
|
if (record?.checkbox) {
|
||||||
@ -195,5 +200,5 @@ export function usePartParameterFields(): ApiFormFieldSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [fieldType, choices]);
|
}, [editTemplate, fieldType, choices]);
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import {
|
|||||||
IconCornerUpRightDouble,
|
IconCornerUpRightDouble,
|
||||||
IconCurrencyDollar,
|
IconCurrencyDollar,
|
||||||
IconDots,
|
IconDots,
|
||||||
|
IconEdit,
|
||||||
IconExclamationCircle,
|
IconExclamationCircle,
|
||||||
IconExternalLink,
|
IconExternalLink,
|
||||||
IconFileUpload,
|
IconFileUpload,
|
||||||
@ -109,6 +110,7 @@ const icons = {
|
|||||||
units: IconRulerMeasure,
|
units: IconRulerMeasure,
|
||||||
keywords: IconTag,
|
keywords: IconTag,
|
||||||
status: IconInfoCircle,
|
status: IconInfoCircle,
|
||||||
|
edit: IconEdit,
|
||||||
info: IconInfoCircle,
|
info: IconInfoCircle,
|
||||||
exclamation: IconExclamationCircle,
|
exclamation: IconExclamationCircle,
|
||||||
details: IconInfoCircle,
|
details: IconInfoCircle,
|
||||||
|
@ -81,7 +81,7 @@ const defaultPageSize: number = 25;
|
|||||||
* @param dataFormatter : (data: any) => any - Callback function to reformat data returned by server (if not in default format)
|
* @param dataFormatter : (data: any) => any - Callback function to reformat data returned by server (if not in default format)
|
||||||
* @param rowActions : (record: any) => RowAction[] - Callback function to generate row actions
|
* @param rowActions : (record: any) => RowAction[] - Callback function to generate row actions
|
||||||
* @param onRowClick : (record: any, index: number, event: any) => void - Callback function when a row is clicked
|
* @param onRowClick : (record: any, index: number, event: any) => void - Callback function when a row is clicked
|
||||||
* @param onCellClick : (event: any, record: any, recordIndex: number, column: any, columnIndex: number) => void - Callback function when a cell is clicked
|
* @param onCellClick : (event: any, record: any, index: number, column: any, columnIndex: number) => void - Callback function when a cell is clicked
|
||||||
* @param modelType: ModelType - The model type for the table
|
* @param modelType: ModelType - The model type for the table
|
||||||
*/
|
*/
|
||||||
export type InvenTreeTableProps<T = any> = {
|
export type InvenTreeTableProps<T = any> = {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Divider, Group, HoverCard, Stack, Text } from '@mantine/core';
|
import { Divider, Group, HoverCard, Stack, Text } from '@mantine/core';
|
||||||
import { IconInfoCircle } from '@tabler/icons-react';
|
import { IconInfoCircle } from '@tabler/icons-react';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode, useMemo } from 'react';
|
||||||
|
|
||||||
import { InvenTreeIcon, InvenTreeIconType } from '../functions/icons';
|
import { InvenTreeIcon, InvenTreeIconType } from '../functions/icons';
|
||||||
|
|
||||||
@ -23,12 +23,26 @@ export function TableHoverCard({
|
|||||||
icon?: InvenTreeIconType;
|
icon?: InvenTreeIconType;
|
||||||
iconColor?: string;
|
iconColor?: string;
|
||||||
}) {
|
}) {
|
||||||
// If no extra information presented, just return the raw value
|
const extraItems: ReactNode = useMemo(() => {
|
||||||
if (!extra) {
|
if (Array.isArray(extra)) {
|
||||||
return value;
|
if (extra.length == 0) {
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(extra) && extra.length == 0) {
|
return (
|
||||||
|
<Stack gap="xs">
|
||||||
|
{extra.map((item, idx) => (
|
||||||
|
<div key={t`item-${idx}`}>{item}</div>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return extra;
|
||||||
|
}
|
||||||
|
}, [extra]);
|
||||||
|
|
||||||
|
// If no extra information presented, just return the raw value
|
||||||
|
if (!extraItems) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +64,7 @@ export function TableHoverCard({
|
|||||||
<Text fw="bold">{title}</Text>
|
<Text fw="bold">{title}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Divider />
|
<Divider />
|
||||||
{extra}
|
{extraItems}
|
||||||
</Stack>
|
</Stack>
|
||||||
</HoverCard.Dropdown>
|
</HoverCard.Dropdown>
|
||||||
</HoverCard>
|
</HoverCard>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { ActionIcon, Group, Tooltip } from '@mantine/core';
|
import { Group } from '@mantine/core';
|
||||||
import { useHover } from '@mantine/hooks';
|
import { useHover } from '@mantine/hooks';
|
||||||
import { IconEdit } from '@tabler/icons-react';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { ReactNode, useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { api } from '../../App';
|
import { api } from '../../App';
|
||||||
import { YesNoButton } from '../../components/buttons/YesNoButton';
|
import { YesNoButton } from '../../components/buttons/YesNoButton';
|
||||||
@ -13,6 +13,8 @@ import { ModelType } from '../../enums/ModelType';
|
|||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { usePartParameterFields } from '../../forms/PartForms';
|
import { usePartParameterFields } from '../../forms/PartForms';
|
||||||
import { cancelEvent } from '../../functions/events';
|
import { cancelEvent } from '../../functions/events';
|
||||||
|
import { navigateToLink } from '../../functions/navigation';
|
||||||
|
import { getDetailUrl } from '../../functions/urls';
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
useEditApiFormModal
|
useEditApiFormModal
|
||||||
@ -30,28 +32,32 @@ import { TableHoverCard } from '../TableHoverCard';
|
|||||||
function ParameterCell({
|
function ParameterCell({
|
||||||
record,
|
record,
|
||||||
template,
|
template,
|
||||||
canEdit,
|
canEdit
|
||||||
onEdit
|
|
||||||
}: {
|
}: {
|
||||||
record: any;
|
record: any;
|
||||||
template: any;
|
template: any;
|
||||||
canEdit: boolean;
|
canEdit: boolean;
|
||||||
onEdit: () => void;
|
|
||||||
}) {
|
}) {
|
||||||
const { hovered, ref } = useHover();
|
const { hovered, ref } = useHover();
|
||||||
|
|
||||||
// Find matching template parameter
|
// Find matching template parameter
|
||||||
let parameter = record.parameters?.find(
|
const parameter = useMemo(() => {
|
||||||
(p: any) => p.template == template.pk
|
return record.parameters?.find((p: any) => p.template == template.pk);
|
||||||
);
|
}, [record.parameters, template]);
|
||||||
|
|
||||||
let extra: any[] = [];
|
let extra: any[] = [];
|
||||||
|
|
||||||
let value: any = parameter?.data;
|
// Format the value for display
|
||||||
|
const value: ReactNode = useMemo(() => {
|
||||||
|
let v: any = parameter?.data;
|
||||||
|
|
||||||
if (template?.checkbox && value != undefined) {
|
// Handle boolean values
|
||||||
value = <YesNoButton value={parameter.data} />;
|
if (template?.checkbox && v != undefined) {
|
||||||
}
|
v = <YesNoButton value={parameter.data} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}, [parameter, template]);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
template.units &&
|
template.units &&
|
||||||
@ -62,30 +68,21 @@ function ParameterCell({
|
|||||||
extra.push(`${parameter.data_numeric} [${template.units}]`);
|
extra.push(`${parameter.data_numeric} [${template.units}]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClick = useCallback((event: any) => {
|
if (hovered && canEdit) {
|
||||||
cancelEvent(event);
|
extra.push(t`Click to edit`);
|
||||||
onEdit();
|
}
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Group grow ref={ref} justify="space-between">
|
<Group grow ref={ref} justify="space-between">
|
||||||
<Group grow style={{ flex: 1 }}>
|
<Group grow>
|
||||||
<TableHoverCard
|
<TableHoverCard
|
||||||
value={value ?? '-'}
|
value={value ?? '-'}
|
||||||
extra={extra}
|
extra={extra}
|
||||||
|
icon={hovered && canEdit ? 'edit' : 'info'}
|
||||||
title={t`Internal Units`}
|
title={t`Internal Units`}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
{hovered && canEdit && (
|
|
||||||
<div style={{ flex: 0 }}>
|
|
||||||
<Tooltip label={t`Edit parameter`}>
|
|
||||||
<ActionIcon size="xs" onClick={handleClick} variant="transparent">
|
|
||||||
<IconEdit />
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Group>
|
</Group>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -98,6 +95,7 @@ export default function ParametricPartTable({
|
|||||||
}) {
|
}) {
|
||||||
const table = useTable('parametric-parts');
|
const table = useTable('parametric-parts');
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const categoryParmeters = useQuery({
|
const categoryParmeters = useQuery({
|
||||||
queryKey: ['category-parameters', categoryId],
|
queryKey: ['category-parameters', categoryId],
|
||||||
@ -118,7 +116,9 @@ export default function ParametricPartTable({
|
|||||||
const [selectedTemplate, setSelectedTemplate] = useState<number>(0);
|
const [selectedTemplate, setSelectedTemplate] = useState<number>(0);
|
||||||
const [selectedParameter, setSelectedParameter] = useState<number>(0);
|
const [selectedParameter, setSelectedParameter] = useState<number>(0);
|
||||||
|
|
||||||
const partParameterFields: ApiFormFieldSet = usePartParameterFields();
|
const partParameterFields: ApiFormFieldSet = usePartParameterFields({
|
||||||
|
editTemplate: false
|
||||||
|
});
|
||||||
|
|
||||||
const addParameter = useCreateApiFormModal({
|
const addParameter = useCreateApiFormModal({
|
||||||
url: ApiEndpoints.part_parameter_list,
|
url: ApiEndpoints.part_parameter_list,
|
||||||
@ -195,26 +195,25 @@ export default function ParametricPartTable({
|
|||||||
record={record}
|
record={record}
|
||||||
template={template}
|
template={template}
|
||||||
canEdit={user.hasChangeRole(UserRoles.part)}
|
canEdit={user.hasChangeRole(UserRoles.part)}
|
||||||
onEdit={() => {
|
|
||||||
setSelectedTemplate(template.pk);
|
|
||||||
setSelectedPart(record.pk);
|
|
||||||
let parameter = record.parameters?.find(
|
|
||||||
(p: any) => p.template == template.pk
|
|
||||||
);
|
|
||||||
|
|
||||||
if (parameter) {
|
|
||||||
setSelectedParameter(parameter.pk);
|
|
||||||
editParameter.open();
|
|
||||||
} else {
|
|
||||||
addParameter.open();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [user, categoryParmeters.data]);
|
}, [user, categoryParmeters.data]);
|
||||||
|
|
||||||
|
const onParameterClick = useCallback((template: number, part: any) => {
|
||||||
|
setSelectedTemplate(template);
|
||||||
|
setSelectedPart(part.pk);
|
||||||
|
let parameter = part.parameters?.find((p: any) => p.template == template);
|
||||||
|
|
||||||
|
if (parameter) {
|
||||||
|
setSelectedParameter(parameter.pk);
|
||||||
|
editParameter.open();
|
||||||
|
} else {
|
||||||
|
addParameter.open();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const tableFilters: TableFilter[] = useMemo(() => {
|
const tableFilters: TableFilter[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -268,14 +267,28 @@ export default function ParametricPartTable({
|
|||||||
columns={tableColumns}
|
columns={tableColumns}
|
||||||
props={{
|
props={{
|
||||||
enableDownload: false,
|
enableDownload: false,
|
||||||
|
tableFilters: tableFilters,
|
||||||
params: {
|
params: {
|
||||||
category: categoryId,
|
category: categoryId,
|
||||||
cascade: true,
|
cascade: true,
|
||||||
category_detail: true,
|
category_detail: true,
|
||||||
parameters: true
|
parameters: true
|
||||||
},
|
},
|
||||||
modelType: ModelType.part,
|
onCellClick: ({ event, record, index, column, columnIndex }) => {
|
||||||
tableFilters: tableFilters
|
cancelEvent(event);
|
||||||
|
|
||||||
|
// Is this a "parameter" cell?
|
||||||
|
if (column?.accessor?.toString()?.startsWith('parameter_')) {
|
||||||
|
const col = column as any;
|
||||||
|
onParameterClick(col.extra.template, record);
|
||||||
|
} else {
|
||||||
|
// Navigate through to the part detail page
|
||||||
|
if (record?.pk) {
|
||||||
|
const url = getDetailUrl(ModelType.part, record.pk);
|
||||||
|
navigateToLink(url, navigate, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -105,7 +105,7 @@ export function PartParameterTable({
|
|||||||
];
|
];
|
||||||
}, [partId]);
|
}, [partId]);
|
||||||
|
|
||||||
const partParameterFields: ApiFormFieldSet = usePartParameterFields();
|
const partParameterFields: ApiFormFieldSet = usePartParameterFields({});
|
||||||
|
|
||||||
const newParameter = useCreateApiFormModal({
|
const newParameter = useCreateApiFormModal({
|
||||||
url: ApiEndpoints.part_parameter_list,
|
url: ApiEndpoints.part_parameter_list,
|
||||||
|
Loading…
Reference in New Issue
Block a user