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:
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/core/macro';
|
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 { type ReactNode, useMemo } from 'react';
|
||||||
|
|
||||||
import { ApiImage } from './ApiImage';
|
import { ApiImage } from './ApiImage';
|
||||||
@ -13,7 +13,9 @@ export function Thumbnail({
|
|||||||
size = 20,
|
size = 20,
|
||||||
link,
|
link,
|
||||||
text,
|
text,
|
||||||
align
|
align,
|
||||||
|
hover,
|
||||||
|
hoverSize = 128
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
src?: string;
|
src?: string;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
@ -21,6 +23,8 @@ export function Thumbnail({
|
|||||||
text?: ReactNode;
|
text?: ReactNode;
|
||||||
align?: string;
|
align?: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
|
hover?: boolean;
|
||||||
|
hoverSize?: number;
|
||||||
}>) {
|
}>) {
|
||||||
const backup_image = '/static/img/blank_image.png';
|
const backup_image = '/static/img/blank_image.png';
|
||||||
|
|
||||||
@ -37,16 +41,35 @@ export function Thumbnail({
|
|||||||
}, [link, text]);
|
}, [link, text]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group align={align ?? 'left'} gap='xs' wrap='nowrap'>
|
<HoverCard
|
||||||
<ApiImage
|
disabled={!hover}
|
||||||
src={src || backup_image}
|
withinPortal
|
||||||
aria-label={alt}
|
position='left'
|
||||||
w={size}
|
shadow='xs'
|
||||||
fit='contain'
|
closeDelay={100}
|
||||||
radius='xs'
|
>
|
||||||
style={{ maxHeight: size }}
|
<HoverCard.Target>
|
||||||
/>
|
<Group align={align ?? 'left'} gap='xs' wrap='nowrap'>
|
||||||
{inner}
|
<ApiImage
|
||||||
</Group>
|
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ export function PartColumn({
|
|||||||
<Thumbnail
|
<Thumbnail
|
||||||
src={part?.thumbnail ?? part?.image}
|
src={part?.thumbnail ?? part?.image}
|
||||||
text={full_name ? part?.full_name : part?.name}
|
text={full_name ? part?.full_name : part?.name}
|
||||||
|
hover
|
||||||
/>
|
/>
|
||||||
<Group justify='flex-end' wrap='nowrap' gap='xs'>
|
<Group justify='flex-end' wrap='nowrap' gap='xs'>
|
||||||
{part?.active == false && (
|
{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 {
|
export function LocationColumn(props: TableColumnProps): TableColumn {
|
||||||
return {
|
return {
|
||||||
accessor: 'location',
|
accessor: 'location',
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { Group, Text } from '@mantine/core';
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
@ -10,7 +9,6 @@ import { apiUrl } from '@lib/functions/Api';
|
|||||||
import { navigateToLink } from '@lib/functions/Navigation';
|
import { navigateToLink } from '@lib/functions/Navigation';
|
||||||
import type { TableFilter } from '@lib/types/Filters';
|
import type { TableFilter } from '@lib/types/Filters';
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
import { Thumbnail } from '../../components/images/Thumbnail';
|
|
||||||
import { companyFields } from '../../forms/CompanyForms';
|
import { companyFields } from '../../forms/CompanyForms';
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
@ -18,7 +16,11 @@ import {
|
|||||||
} from '../../hooks/UseForm';
|
} from '../../hooks/UseForm';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import { BooleanColumn, DescriptionColumn } from '../ColumnRenderers';
|
import {
|
||||||
|
BooleanColumn,
|
||||||
|
CompanyColumn,
|
||||||
|
DescriptionColumn
|
||||||
|
} from '../ColumnRenderers';
|
||||||
import { InvenTreeTable } from '../InvenTreeTable';
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
import { type RowAction, RowEditAction } from '../RowActions';
|
import { type RowAction, RowEditAction } from '../RowActions';
|
||||||
|
|
||||||
@ -45,16 +47,7 @@ export function CompanyTable({
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
switchable: false,
|
switchable: false,
|
||||||
render: (record: any) => {
|
render: (record: any) => {
|
||||||
return (
|
return <CompanyColumn company={record} />;
|
||||||
<Group gap='xs' wrap='nowrap'>
|
|
||||||
<Thumbnail
|
|
||||||
src={record.thumbnail ?? record.image ?? ''}
|
|
||||||
alt={record.name}
|
|
||||||
size={24}
|
|
||||||
/>
|
|
||||||
<Text>{record.name}</Text>
|
|
||||||
</Group>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DescriptionColumn({}),
|
DescriptionColumn({}),
|
||||||
|
@ -6,7 +6,6 @@ import { ModelType } from '@lib/enums/ModelType';
|
|||||||
import { UserRoles } from '@lib/enums/Roles';
|
import { UserRoles } from '@lib/enums/Roles';
|
||||||
import { apiUrl } from '@lib/functions/Api';
|
import { apiUrl } from '@lib/functions/Api';
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
import { Thumbnail } from '../../components/images/Thumbnail';
|
|
||||||
import { useManufacturerPartFields } from '../../forms/CompanyForms';
|
import { useManufacturerPartFields } from '../../forms/CompanyForms';
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
@ -16,7 +15,12 @@ import {
|
|||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import type { TableColumn } from '../Column';
|
import type { TableColumn } from '../Column';
|
||||||
import { DescriptionColumn, LinkColumn, PartColumn } from '../ColumnRenderers';
|
import {
|
||||||
|
CompanyColumn,
|
||||||
|
DescriptionColumn,
|
||||||
|
LinkColumn,
|
||||||
|
PartColumn
|
||||||
|
} from '../ColumnRenderers';
|
||||||
import { InvenTreeTable } from '../InvenTreeTable';
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
import { type RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
import { type RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
||||||
|
|
||||||
@ -42,16 +46,9 @@ export function ManufacturerPartTable({
|
|||||||
{
|
{
|
||||||
accessor: 'manufacturer',
|
accessor: 'manufacturer',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (record: any) => {
|
render: (record: any) => (
|
||||||
const manufacturer = record?.manufacturer_detail ?? {};
|
<CompanyColumn company={record?.manufacturer_detail} />
|
||||||
|
)
|
||||||
return (
|
|
||||||
<Thumbnail
|
|
||||||
src={manufacturer?.thumbnail ?? manufacturer.image}
|
|
||||||
text={manufacturer.name}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'MPN',
|
accessor: 'MPN',
|
||||||
|
@ -7,13 +7,13 @@ import { UserRoles } from '@lib/enums/Roles';
|
|||||||
import { apiUrl } from '@lib/functions/Api';
|
import { apiUrl } from '@lib/functions/Api';
|
||||||
import type { TableFilter } from '@lib/types/Filters';
|
import type { TableFilter } from '@lib/types/Filters';
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
import { Thumbnail } from '../../components/images/Thumbnail';
|
|
||||||
import { formatCurrency } from '../../defaults/formatters';
|
import { formatCurrency } from '../../defaults/formatters';
|
||||||
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
|
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
|
||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import {
|
import {
|
||||||
|
CompanyColumn,
|
||||||
CompletionDateColumn,
|
CompletionDateColumn,
|
||||||
CreatedByColumn,
|
CreatedByColumn,
|
||||||
CreationDateColumn,
|
CreationDateColumn,
|
||||||
@ -106,17 +106,9 @@ export function PurchaseOrderTable({
|
|||||||
accessor: 'supplier__name',
|
accessor: 'supplier__name',
|
||||||
title: t`Supplier`,
|
title: t`Supplier`,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (record: any) => {
|
render: (record: any) => (
|
||||||
const supplier = record.supplier_detail ?? {};
|
<CompanyColumn company={record.supplier_detail} />
|
||||||
|
)
|
||||||
return (
|
|
||||||
<Thumbnail
|
|
||||||
src={supplier?.image}
|
|
||||||
alt={supplier.name}
|
|
||||||
text={supplier.name}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'supplier_reference'
|
accessor: 'supplier_reference'
|
||||||
|
@ -8,7 +8,6 @@ import { UserRoles } from '@lib/enums/Roles';
|
|||||||
import { apiUrl } from '@lib/functions/Api';
|
import { apiUrl } from '@lib/functions/Api';
|
||||||
import type { TableFilter } from '@lib/types/Filters';
|
import type { TableFilter } from '@lib/types/Filters';
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
import { Thumbnail } from '../../components/images/Thumbnail';
|
|
||||||
import { useSupplierPartFields } from '../../forms/CompanyForms';
|
import { useSupplierPartFields } from '../../forms/CompanyForms';
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
@ -20,6 +19,7 @@ import { useUserState } from '../../states/UserState';
|
|||||||
import type { TableColumn } from '../Column';
|
import type { TableColumn } from '../Column';
|
||||||
import {
|
import {
|
||||||
BooleanColumn,
|
BooleanColumn,
|
||||||
|
CompanyColumn,
|
||||||
DescriptionColumn,
|
DescriptionColumn,
|
||||||
LinkColumn,
|
LinkColumn,
|
||||||
NoteColumn,
|
NoteColumn,
|
||||||
@ -53,18 +53,9 @@ export function SupplierPartTable({
|
|||||||
{
|
{
|
||||||
accessor: 'supplier',
|
accessor: 'supplier',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (record: any) => {
|
render: (record: any) => (
|
||||||
const supplier = record?.supplier_detail ?? {};
|
<CompanyColumn company={record?.supplier_detail} />
|
||||||
|
)
|
||||||
return supplier?.pk ? (
|
|
||||||
<Thumbnail
|
|
||||||
src={supplier?.thumbnail ?? supplier.image}
|
|
||||||
text={supplier.name}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
'-'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'SKU',
|
accessor: 'SKU',
|
||||||
@ -76,18 +67,9 @@ export function SupplierPartTable({
|
|||||||
accessor: 'manufacturer',
|
accessor: 'manufacturer',
|
||||||
title: t`Manufacturer`,
|
title: t`Manufacturer`,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (record: any) => {
|
render: (record: any) => (
|
||||||
const manufacturer = record?.manufacturer_detail ?? {};
|
<CompanyColumn company={record?.manufacturer_detail} />
|
||||||
|
)
|
||||||
return manufacturer?.pk ? (
|
|
||||||
<Thumbnail
|
|
||||||
src={manufacturer?.thumbnail ?? manufacturer.image}
|
|
||||||
text={manufacturer.name}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
'-'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'MPN',
|
accessor: 'MPN',
|
||||||
|
@ -9,7 +9,6 @@ import { apiUrl } from '@lib/functions/Api';
|
|||||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||||
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
import { Thumbnail } from '../../components/images/Thumbnail';
|
|
||||||
import { formatCurrency } from '../../defaults/formatters';
|
import { formatCurrency } from '../../defaults/formatters';
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
@ -19,6 +18,7 @@ import {
|
|||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import type { TableColumn } from '../Column';
|
import type { TableColumn } from '../Column';
|
||||||
|
import { CompanyColumn } from '../ColumnRenderers';
|
||||||
import { InvenTreeTable } from '../InvenTreeTable';
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
import { type RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
import { type RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
||||||
|
|
||||||
@ -36,21 +36,9 @@ export function SupplierPriceBreakColumns(): TableColumn[] {
|
|||||||
title: t`Supplier`,
|
title: t`Supplier`,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
switchable: true,
|
switchable: true,
|
||||||
render: (record: any) => {
|
render: (record: any) => (
|
||||||
return (
|
<CompanyColumn company={record.supplier_detail} />
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'part_detail.SKU',
|
accessor: 'part_detail.SKU',
|
||||||
|
@ -7,13 +7,13 @@ import { UserRoles } from '@lib/enums/Roles';
|
|||||||
import { apiUrl } from '@lib/functions/Api';
|
import { apiUrl } from '@lib/functions/Api';
|
||||||
import type { TableFilter } from '@lib/types/Filters';
|
import type { TableFilter } from '@lib/types/Filters';
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
import { Thumbnail } from '../../components/images/Thumbnail';
|
|
||||||
import { formatCurrency } from '../../defaults/formatters';
|
import { formatCurrency } from '../../defaults/formatters';
|
||||||
import { useReturnOrderFields } from '../../forms/ReturnOrderForms';
|
import { useReturnOrderFields } from '../../forms/ReturnOrderForms';
|
||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import {
|
import {
|
||||||
|
CompanyColumn,
|
||||||
CompletionDateColumn,
|
CompletionDateColumn,
|
||||||
CreatedByColumn,
|
CreatedByColumn,
|
||||||
CreationDateColumn,
|
CreationDateColumn,
|
||||||
@ -111,17 +111,9 @@ export function ReturnOrderTable({
|
|||||||
accessor: 'customer__name',
|
accessor: 'customer__name',
|
||||||
title: t`Customer`,
|
title: t`Customer`,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (record: any) => {
|
render: (record: any) => (
|
||||||
const customer = record.customer_detail ?? {};
|
<CompanyColumn company={record.customer_detail} />
|
||||||
|
)
|
||||||
return (
|
|
||||||
<Thumbnail
|
|
||||||
src={customer?.image}
|
|
||||||
alt={customer.name}
|
|
||||||
text={customer.name}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'customer_reference'
|
accessor: 'customer_reference'
|
||||||
|
@ -7,7 +7,6 @@ import { UserRoles } from '@lib/enums/Roles';
|
|||||||
import { apiUrl } from '@lib/functions/Api';
|
import { apiUrl } from '@lib/functions/Api';
|
||||||
import type { TableFilter } from '@lib/types/Filters';
|
import type { TableFilter } from '@lib/types/Filters';
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
import { Thumbnail } from '../../components/images/Thumbnail';
|
|
||||||
import { ProgressBar } from '../../components/items/ProgressBar';
|
import { ProgressBar } from '../../components/items/ProgressBar';
|
||||||
import { formatCurrency } from '../../defaults/formatters';
|
import { formatCurrency } from '../../defaults/formatters';
|
||||||
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
|
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
|
||||||
@ -15,6 +14,7 @@ import { useCreateApiFormModal } from '../../hooks/UseForm';
|
|||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import {
|
import {
|
||||||
|
CompanyColumn,
|
||||||
CreatedByColumn,
|
CreatedByColumn,
|
||||||
CreationDateColumn,
|
CreationDateColumn,
|
||||||
DescriptionColumn,
|
DescriptionColumn,
|
||||||
@ -136,17 +136,9 @@ export function SalesOrderTable({
|
|||||||
accessor: 'customer__name',
|
accessor: 'customer__name',
|
||||||
title: t`Customer`,
|
title: t`Customer`,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (record: any) => {
|
render: (record: any) => (
|
||||||
const customer = record.customer_detail ?? {};
|
<CompanyColumn company={record.customer_detail} />
|
||||||
|
)
|
||||||
return (
|
|
||||||
<Thumbnail
|
|
||||||
src={customer?.image}
|
|
||||||
alt={customer.name}
|
|
||||||
text={customer.name}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'customer_reference',
|
accessor: 'customer_reference',
|
||||||
|
Reference in New Issue
Block a user