2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-02 03:30:54 +00:00

[UI] Hover image (#9907)

* Add "hover image" for part display in tables

* Refactor company column
This commit is contained in:
Oliver
2025-06-30 13:49:43 +10:00
committed by GitHub
parent 8c6cacae66
commit 1abf9b30dd
9 changed files with 95 additions and 115 deletions

View File

@ -1,5 +1,5 @@
import { t } from '@lingui/core/macro';
import { Anchor, Group } from '@mantine/core';
import { Anchor, Group, HoverCard, Image } from '@mantine/core';
import { type ReactNode, useMemo } from 'react';
import { ApiImage } from './ApiImage';
@ -13,7 +13,9 @@ export function Thumbnail({
size = 20,
link,
text,
align
align,
hover,
hoverSize = 128
}: Readonly<{
src?: string;
alt?: string;
@ -21,6 +23,8 @@ export function Thumbnail({
text?: ReactNode;
align?: string;
link?: string;
hover?: boolean;
hoverSize?: number;
}>) {
const backup_image = '/static/img/blank_image.png';
@ -37,16 +41,35 @@ export function Thumbnail({
}, [link, text]);
return (
<Group align={align ?? 'left'} gap='xs' wrap='nowrap'>
<ApiImage
src={src || backup_image}
aria-label={alt}
w={size}
fit='contain'
radius='xs'
style={{ maxHeight: size }}
/>
{inner}
</Group>
<HoverCard
disabled={!hover}
withinPortal
position='left'
shadow='xs'
closeDelay={100}
>
<HoverCard.Target>
<Group align={align ?? 'left'} gap='xs' wrap='nowrap'>
<ApiImage
src={src || backup_image}
aria-label={alt}
w={size}
fit='contain'
radius='xs'
style={{ maxHeight: size }}
/>
{inner}
</Group>
</HoverCard.Target>
<HoverCard.Dropdown>
<Image
src={src || backup_image}
alt={alt}
w={hoverSize}
fit='contain'
style={{ maxHeight: hoverSize }}
/>
</HoverCard.Dropdown>
</HoverCard>
);
}

View File

@ -31,6 +31,7 @@ export function PartColumn({
<Thumbnail
src={part?.thumbnail ?? part?.image}
text={full_name ? part?.full_name : part?.name}
hover
/>
<Group justify='flex-end' wrap='nowrap' gap='xs'>
{part?.active == false && (
@ -55,6 +56,26 @@ export function PartColumn({
);
}
export function CompanyColumn({
company
}: {
company: any;
}) {
return company ? (
<Group gap='xs' wrap='nowrap'>
<Thumbnail
src={company.thumbnail ?? company.image ?? ''}
alt={company.name}
size={24}
hover
/>
<Text>{company.name}</Text>
</Group>
) : (
<Skeleton />
);
}
export function LocationColumn(props: TableColumnProps): TableColumn {
return {
accessor: 'location',

View File

@ -1,5 +1,4 @@
import { t } from '@lingui/core/macro';
import { Group, Text } from '@mantine/core';
import { useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
@ -10,7 +9,6 @@ import { apiUrl } from '@lib/functions/Api';
import { navigateToLink } from '@lib/functions/Navigation';
import type { TableFilter } from '@lib/types/Filters';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { Thumbnail } from '../../components/images/Thumbnail';
import { companyFields } from '../../forms/CompanyForms';
import {
useCreateApiFormModal,
@ -18,7 +16,11 @@ import {
} from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { useUserState } from '../../states/UserState';
import { BooleanColumn, DescriptionColumn } from '../ColumnRenderers';
import {
BooleanColumn,
CompanyColumn,
DescriptionColumn
} from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
import { type RowAction, RowEditAction } from '../RowActions';
@ -45,16 +47,7 @@ export function CompanyTable({
sortable: true,
switchable: false,
render: (record: any) => {
return (
<Group gap='xs' wrap='nowrap'>
<Thumbnail
src={record.thumbnail ?? record.image ?? ''}
alt={record.name}
size={24}
/>
<Text>{record.name}</Text>
</Group>
);
return <CompanyColumn company={record} />;
}
},
DescriptionColumn({}),

View File

@ -6,7 +6,6 @@ import { ModelType } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { Thumbnail } from '../../components/images/Thumbnail';
import { useManufacturerPartFields } from '../../forms/CompanyForms';
import {
useCreateApiFormModal,
@ -16,7 +15,12 @@ import {
import { useTable } from '../../hooks/UseTable';
import { useUserState } from '../../states/UserState';
import type { TableColumn } from '../Column';
import { DescriptionColumn, LinkColumn, PartColumn } from '../ColumnRenderers';
import {
CompanyColumn,
DescriptionColumn,
LinkColumn,
PartColumn
} from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
import { type RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
@ -42,16 +46,9 @@ export function ManufacturerPartTable({
{
accessor: 'manufacturer',
sortable: true,
render: (record: any) => {
const manufacturer = record?.manufacturer_detail ?? {};
return (
<Thumbnail
src={manufacturer?.thumbnail ?? manufacturer.image}
text={manufacturer.name}
/>
);
}
render: (record: any) => (
<CompanyColumn company={record?.manufacturer_detail} />
)
},
{
accessor: 'MPN',

View File

@ -7,13 +7,13 @@ import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api';
import type { TableFilter } from '@lib/types/Filters';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { Thumbnail } from '../../components/images/Thumbnail';
import { formatCurrency } from '../../defaults/formatters';
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { useUserState } from '../../states/UserState';
import {
CompanyColumn,
CompletionDateColumn,
CreatedByColumn,
CreationDateColumn,
@ -106,17 +106,9 @@ export function PurchaseOrderTable({
accessor: 'supplier__name',
title: t`Supplier`,
sortable: true,
render: (record: any) => {
const supplier = record.supplier_detail ?? {};
return (
<Thumbnail
src={supplier?.image}
alt={supplier.name}
text={supplier.name}
/>
);
}
render: (record: any) => (
<CompanyColumn company={record.supplier_detail} />
)
},
{
accessor: 'supplier_reference'

View File

@ -8,7 +8,6 @@ import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api';
import type { TableFilter } from '@lib/types/Filters';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { Thumbnail } from '../../components/images/Thumbnail';
import { useSupplierPartFields } from '../../forms/CompanyForms';
import {
useCreateApiFormModal,
@ -20,6 +19,7 @@ import { useUserState } from '../../states/UserState';
import type { TableColumn } from '../Column';
import {
BooleanColumn,
CompanyColumn,
DescriptionColumn,
LinkColumn,
NoteColumn,
@ -53,18 +53,9 @@ export function SupplierPartTable({
{
accessor: 'supplier',
sortable: true,
render: (record: any) => {
const supplier = record?.supplier_detail ?? {};
return supplier?.pk ? (
<Thumbnail
src={supplier?.thumbnail ?? supplier.image}
text={supplier.name}
/>
) : (
'-'
);
}
render: (record: any) => (
<CompanyColumn company={record?.supplier_detail} />
)
},
{
accessor: 'SKU',
@ -76,18 +67,9 @@ export function SupplierPartTable({
accessor: 'manufacturer',
title: t`Manufacturer`,
sortable: true,
render: (record: any) => {
const manufacturer = record?.manufacturer_detail ?? {};
return manufacturer?.pk ? (
<Thumbnail
src={manufacturer?.thumbnail ?? manufacturer.image}
text={manufacturer.name}
/>
) : (
'-'
);
}
render: (record: any) => (
<CompanyColumn company={record?.manufacturer_detail} />
)
},
{
accessor: 'MPN',

View File

@ -9,7 +9,6 @@ import { apiUrl } from '@lib/functions/Api';
import { getDetailUrl } from '@lib/functions/Navigation';
import type { ApiFormFieldSet } from '@lib/types/Forms';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { Thumbnail } from '../../components/images/Thumbnail';
import { formatCurrency } from '../../defaults/formatters';
import {
useCreateApiFormModal,
@ -19,6 +18,7 @@ import {
import { useTable } from '../../hooks/UseTable';
import { useUserState } from '../../states/UserState';
import type { TableColumn } from '../Column';
import { CompanyColumn } from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
import { type RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
@ -36,21 +36,9 @@ export function SupplierPriceBreakColumns(): TableColumn[] {
title: t`Supplier`,
sortable: true,
switchable: true,
render: (record: any) => {
return (
<Group gap='xs' wrap='nowrap'>
<Thumbnail
src={
record?.supplier_detail?.thumbnail ??
record?.supplier_detail?.image
}
alt={record?.supplier_detail?.name}
size={24}
/>
<Text>{record.supplier_detail?.name}</Text>
</Group>
);
}
render: (record: any) => (
<CompanyColumn company={record.supplier_detail} />
)
},
{
accessor: 'part_detail.SKU',

View File

@ -7,13 +7,13 @@ import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api';
import type { TableFilter } from '@lib/types/Filters';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { Thumbnail } from '../../components/images/Thumbnail';
import { formatCurrency } from '../../defaults/formatters';
import { useReturnOrderFields } from '../../forms/ReturnOrderForms';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { useUserState } from '../../states/UserState';
import {
CompanyColumn,
CompletionDateColumn,
CreatedByColumn,
CreationDateColumn,
@ -111,17 +111,9 @@ export function ReturnOrderTable({
accessor: 'customer__name',
title: t`Customer`,
sortable: true,
render: (record: any) => {
const customer = record.customer_detail ?? {};
return (
<Thumbnail
src={customer?.image}
alt={customer.name}
text={customer.name}
/>
);
}
render: (record: any) => (
<CompanyColumn company={record.customer_detail} />
)
},
{
accessor: 'customer_reference'

View File

@ -7,7 +7,6 @@ import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api';
import type { TableFilter } from '@lib/types/Filters';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { Thumbnail } from '../../components/images/Thumbnail';
import { ProgressBar } from '../../components/items/ProgressBar';
import { formatCurrency } from '../../defaults/formatters';
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
@ -15,6 +14,7 @@ import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { useUserState } from '../../states/UserState';
import {
CompanyColumn,
CreatedByColumn,
CreationDateColumn,
DescriptionColumn,
@ -136,17 +136,9 @@ export function SalesOrderTable({
accessor: 'customer__name',
title: t`Customer`,
sortable: true,
render: (record: any) => {
const customer = record.customer_detail ?? {};
return (
<Thumbnail
src={customer?.image}
alt={customer.name}
text={customer.name}
/>
);
}
render: (record: any) => (
<CompanyColumn company={record.customer_detail} />
)
},
{
accessor: 'customer_reference',