[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:
Oliver 2024-08-20 13:13:36 +10:00 committed by GitHub
parent 7cbaeb159e
commit dbe12c2c53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 89 additions and 55 deletions

View File

@ -141,7 +141,11 @@ export function partCategoryFields(): ApiFormFieldSet {
return fields;
}
export function usePartParameterFields(): ApiFormFieldSet {
export function usePartParameterFields({
editTemplate
}: {
editTemplate?: boolean;
}): ApiFormFieldSet {
// Valid field choices
const [choices, setChoices] = useState<any[]>([]);
@ -156,6 +160,7 @@ export function usePartParameterFields(): ApiFormFieldSet {
disabled: true
},
template: {
disabled: editTemplate == false,
onValueChange: (value: any, record: any) => {
// Adjust the type of the "data" field based on the selected template
if (record?.checkbox) {
@ -195,5 +200,5 @@ export function usePartParameterFields(): ApiFormFieldSet {
}
}
};
}, [fieldType, choices]);
}, [editTemplate, fieldType, choices]);
}

View File

@ -27,6 +27,7 @@ import {
IconCornerUpRightDouble,
IconCurrencyDollar,
IconDots,
IconEdit,
IconExclamationCircle,
IconExternalLink,
IconFileUpload,
@ -109,6 +110,7 @@ const icons = {
units: IconRulerMeasure,
keywords: IconTag,
status: IconInfoCircle,
edit: IconEdit,
info: IconInfoCircle,
exclamation: IconExclamationCircle,
details: IconInfoCircle,

View File

@ -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 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 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
*/
export type InvenTreeTableProps<T = any> = {

View File

@ -1,7 +1,7 @@
import { t } from '@lingui/macro';
import { Divider, Group, HoverCard, Stack, Text } from '@mantine/core';
import { IconInfoCircle } from '@tabler/icons-react';
import { ReactNode } from 'react';
import { ReactNode, useMemo } from 'react';
import { InvenTreeIcon, InvenTreeIconType } from '../functions/icons';
@ -23,12 +23,26 @@ export function TableHoverCard({
icon?: InvenTreeIconType;
iconColor?: string;
}) {
// If no extra information presented, just return the raw value
if (!extra) {
return value;
}
const extraItems: ReactNode = useMemo(() => {
if (Array.isArray(extra)) {
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;
}
@ -50,7 +64,7 @@ export function TableHoverCard({
<Text fw="bold">{title}</Text>
</Group>
<Divider />
{extra}
{extraItems}
</Stack>
</HoverCard.Dropdown>
</HoverCard>

View File

@ -1,9 +1,9 @@
import { t } from '@lingui/macro';
import { ActionIcon, Group, Tooltip } from '@mantine/core';
import { Group } from '@mantine/core';
import { useHover } from '@mantine/hooks';
import { IconEdit } from '@tabler/icons-react';
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 { YesNoButton } from '../../components/buttons/YesNoButton';
@ -13,6 +13,8 @@ import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { usePartParameterFields } from '../../forms/PartForms';
import { cancelEvent } from '../../functions/events';
import { navigateToLink } from '../../functions/navigation';
import { getDetailUrl } from '../../functions/urls';
import {
useCreateApiFormModal,
useEditApiFormModal
@ -30,28 +32,32 @@ import { TableHoverCard } from '../TableHoverCard';
function ParameterCell({
record,
template,
canEdit,
onEdit
canEdit
}: {
record: any;
template: any;
canEdit: boolean;
onEdit: () => void;
}) {
const { hovered, ref } = useHover();
// Find matching template parameter
let parameter = record.parameters?.find(
(p: any) => p.template == template.pk
);
const parameter = useMemo(() => {
return record.parameters?.find((p: any) => p.template == template.pk);
}, [record.parameters, template]);
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) {
value = <YesNoButton value={parameter.data} />;
}
// Handle boolean values
if (template?.checkbox && v != undefined) {
v = <YesNoButton value={parameter.data} />;
}
return v;
}, [parameter, template]);
if (
template.units &&
@ -62,30 +68,21 @@ function ParameterCell({
extra.push(`${parameter.data_numeric} [${template.units}]`);
}
const handleClick = useCallback((event: any) => {
cancelEvent(event);
onEdit();
}, []);
if (hovered && canEdit) {
extra.push(t`Click to edit`);
}
return (
<div>
<Group grow ref={ref} justify="space-between">
<Group grow style={{ flex: 1 }}>
<Group grow>
<TableHoverCard
value={value ?? '-'}
extra={extra}
icon={hovered && canEdit ? 'edit' : 'info'}
title={t`Internal Units`}
/>
</Group>
{hovered && canEdit && (
<div style={{ flex: 0 }}>
<Tooltip label={t`Edit parameter`}>
<ActionIcon size="xs" onClick={handleClick} variant="transparent">
<IconEdit />
</ActionIcon>
</Tooltip>
</div>
)}
</Group>
</div>
);
@ -98,6 +95,7 @@ export default function ParametricPartTable({
}) {
const table = useTable('parametric-parts');
const user = useUserState();
const navigate = useNavigate();
const categoryParmeters = useQuery({
queryKey: ['category-parameters', categoryId],
@ -118,7 +116,9 @@ export default function ParametricPartTable({
const [selectedTemplate, setSelectedTemplate] = useState<number>(0);
const [selectedParameter, setSelectedParameter] = useState<number>(0);
const partParameterFields: ApiFormFieldSet = usePartParameterFields();
const partParameterFields: ApiFormFieldSet = usePartParameterFields({
editTemplate: false
});
const addParameter = useCreateApiFormModal({
url: ApiEndpoints.part_parameter_list,
@ -195,26 +195,25 @@ export default function ParametricPartTable({
record={record}
template={template}
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]);
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(() => {
return [
{
@ -268,14 +267,28 @@ export default function ParametricPartTable({
columns={tableColumns}
props={{
enableDownload: false,
tableFilters: tableFilters,
params: {
category: categoryId,
cascade: true,
category_detail: true,
parameters: true
},
modelType: ModelType.part,
tableFilters: tableFilters
onCellClick: ({ event, record, index, column, columnIndex }) => {
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);
}
}
}
}}
/>
</>

View File

@ -105,7 +105,7 @@ export function PartParameterTable({
];
}, [partId]);
const partParameterFields: ApiFormFieldSet = usePartParameterFields();
const partParameterFields: ApiFormFieldSet = usePartParameterFields({});
const newParameter = useCreateApiFormModal({
url: ApiEndpoints.part_parameter_list,