2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-29 12:06:44 +00:00

[UI] Responsiveness Improvements (#8885)

* Shorten string in page title

* Style fixes

* Adjust cell width props

* Refactor <PageDetail> component

- Improve responsiveness

* Simplify <ItemDetailsGrid />

* Refactor <DetailsImage>
This commit is contained in:
Oliver 2025-01-14 10:38:48 +11:00 committed by GitHub
parent ea1b2e3079
commit 46f6450ee1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 224 additions and 189 deletions

View File

@ -380,16 +380,15 @@ export function DetailsTableField({
<Table.Tr style={{ verticalAlign: 'top' }}> <Table.Tr style={{ verticalAlign: 'top' }}>
<Table.Td <Table.Td
style={{ style={{
width: '50',
maxWidth: '50' maxWidth: '50'
}} }}
> >
<InvenTreeIcon icon={field.icon ?? (field.name as InvenTreeIconType)} /> <InvenTreeIcon icon={field.icon ?? (field.name as InvenTreeIconType)} />
</Table.Td> </Table.Td>
<Table.Td style={{ maxWidth: '65%', lineBreak: 'auto' }}> <Table.Td style={{ minWidth: 75, lineBreak: 'auto', flex: 2 }}>
<Text>{field.label}</Text> <Text>{field.label}</Text>
</Table.Td> </Table.Td>
<Table.Td style={{ lineBreak: 'anywhere' }}> <Table.Td style={{ lineBreak: 'anywhere', minWidth: 100, flex: 10 }}>
<FieldType field_data={field} field_value={fieldValue} /> <FieldType field_data={field} field_value={fieldValue} />
</Table.Td> </Table.Td>
<Table.Td style={{ width: '50' }}> <Table.Td style={{ width: '50' }}>
@ -409,7 +408,11 @@ export function DetailsTable({
title?: string; title?: string;
}>) { }>) {
return ( return (
<Paper p='xs' withBorder radius='xs'> <Paper
p='xs'
withBorder
style={{ overflowX: 'hidden', width: '100%', minWidth: 200 }}
>
<Stack gap='xs'> <Stack gap='xs'>
{title && <StylishText size='lg'>{title}</StylishText>} {title && <StylishText size='lg'>{title}</StylishText>}
<Table striped verticalSpacing={5} horizontalSpacing='sm'> <Table striped verticalSpacing={5} horizontalSpacing='sm'>

View File

@ -2,6 +2,7 @@ import { Trans, t } from '@lingui/macro';
import { import {
AspectRatio, AspectRatio,
Button, Button,
Grid,
Group, Group,
Image, Image,
Overlay, Overlay,
@ -421,31 +422,39 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
return ( return (
<> <>
{downloadImage.modal} {downloadImage.modal}
<AspectRatio ref={ref} maw={IMAGE_DIMENSION} ratio={1} pos='relative'> <Grid.Col span={{ base: 12, sm: 4 }}>
<> <AspectRatio
<ApiImage ref={ref}
src={img} maw={IMAGE_DIMENSION}
mah={IMAGE_DIMENSION} ratio={1}
maw={IMAGE_DIMENSION} pos='relative'
onClick={expandImage} visibleFrom='xs'
/> >
{permissions.hasChangeRole(props.appRole) && <>
hasOverlay && <ApiImage
hovered && ( src={img}
<Overlay color='black' opacity={0.8} onClick={expandImage}> mah={IMAGE_DIMENSION}
<ImageActionButtons maw={IMAGE_DIMENSION}
visible={hovered} onClick={expandImage}
actions={props.imageActions} />
apiPath={props.apiPath} {permissions.hasChangeRole(props.appRole) &&
hasImage={!!props.src} hasOverlay &&
pk={props.pk} hovered && (
setImage={setAndRefresh} <Overlay color='black' opacity={0.8} onClick={expandImage}>
downloadImage={downloadImage.open} <ImageActionButtons
/> visible={hovered}
</Overlay> actions={props.imageActions}
)} apiPath={props.apiPath}
</> hasImage={!!props.src}
</AspectRatio> pk={props.pk}
setImage={setAndRefresh}
downloadImage={downloadImage.open}
/>
</Overlay>
)}
</>
</AspectRatio>
</Grid.Col>
</> </>
); );
} }

View File

@ -1,16 +1,15 @@
import { Paper, SimpleGrid } from '@mantine/core'; import { Paper, SimpleGrid } from '@mantine/core';
import { useElementSize } from '@mantine/hooks';
import type React from 'react'; import type React from 'react';
import { useMemo } from 'react';
export function ItemDetailsGrid(props: React.PropsWithChildren<{}>) { export function ItemDetailsGrid(props: React.PropsWithChildren<{}>) {
const { ref, width } = useElementSize();
const cols = useMemo(() => (width > 700 ? 2 : 1), [width]);
return ( return (
<Paper p='xs'> <Paper p='xs'>
<SimpleGrid cols={cols} spacing='xs' verticalSpacing='xs' ref={ref}> <SimpleGrid
cols={{ base: 1, '900px': 2 }}
type='container'
spacing='xs'
verticalSpacing='xs'
>
{props.children} {props.children}
</SimpleGrid> </SimpleGrid>
</Paper> </Paper>

View File

@ -1,7 +1,8 @@
import { Group, Paper, Space, Stack, Text } from '@mantine/core'; import { Group, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { useHotkeys } from '@mantine/hooks'; import { useHotkeys } from '@mantine/hooks';
import { Fragment, type ReactNode } from 'react'; import { Fragment, type ReactNode, useMemo } from 'react';
import { shortenString } from '../../functions/tables';
import { ApiImage } from '../images/ApiImage'; import { ApiImage } from '../images/ApiImage';
import { StylishText } from '../items/StylishText'; import { StylishText } from '../items/StylishText';
import { type Breadcrumb, BreadcrumbList } from './BreadcrumbList'; import { type Breadcrumb, BreadcrumbList } from './BreadcrumbList';
@ -51,9 +52,41 @@ export function PageDetail({
] ]
]); ]);
const pageTitleString = useMemo(
() =>
shortenString({
str: title,
len: 50
}),
[title]
);
const description = useMemo(
() =>
shortenString({
str: subtitle,
len: 75
}),
[subtitle]
);
const maxCols = useMemo(() => {
let cols = 1;
if (!!detail) {
cols++;
}
if (!!badges) {
cols++;
}
return cols;
}, [detail, badges]);
return ( return (
<> <>
<PageTitle title={title} /> <PageTitle title={pageTitleString} />
<Stack gap='xs'> <Stack gap='xs'>
{breadcrumbs && breadcrumbs.length > 0 && ( {breadcrumbs && breadcrumbs.length > 0 && (
<BreadcrumbList <BreadcrumbList
@ -62,8 +95,19 @@ export function PageDetail({
/> />
)} )}
<Paper p='xs' radius='xs' shadow='xs'> <Paper p='xs' radius='xs' shadow='xs'>
<Stack gap='xs'> <Group
<Group justify='space-between' wrap='nowrap'> justify='space-between'
gap='xs'
wrap='nowrap'
align='flex-start'
>
<SimpleGrid
cols={{
base: 1,
md: Math.min(2, maxCols),
lg: Math.min(3, maxCols)
}}
>
<Group justify='left' wrap='nowrap'> <Group justify='left' wrap='nowrap'>
{imageUrl && ( {imageUrl && (
<ApiImage <ApiImage
@ -72,6 +116,7 @@ export function PageDetail({
miw={42} miw={42}
mah={42} mah={42}
maw={42} maw={42}
visibleFrom='sm'
/> />
)} )}
<Stack gap='xs'> <Stack gap='xs'>
@ -79,30 +124,33 @@ export function PageDetail({
{subtitle && ( {subtitle && (
<Group gap='xs'> <Group gap='xs'>
{icon} {icon}
<Text size='sm' truncate> <Text size='sm'>{description}</Text>
{subtitle}
</Text>
</Group> </Group>
)} )}
</Stack> </Stack>
</Group> </Group>
<Space /> {detail && <div>{detail}</div>}
{detail} {badges && (
<Group justify='right' gap='xs' wrap='nowrap'> <Group
{badges?.map((badge, idx) => ( justify='center'
<Fragment key={idx}>{badge}</Fragment> gap='xs'
))} align='flex-start'
</Group> wrap='nowrap'
<Space /> >
{actions && ( {badges?.map((badge, idx) => (
<Group gap={5} justify='right'> <Fragment key={idx}>{badge}</Fragment>
{actions.map((action, idx) => (
<Fragment key={idx}>{action}</Fragment>
))} ))}
</Group> </Group>
)} )}
</Group> </SimpleGrid>
</Stack> {actions && (
<Group gap={5} justify='right' wrap='nowrap' align='flex-start'>
{actions.map((action, idx) => (
<Fragment key={idx}>{action}</Fragment>
))}
</Group>
)}
</Group>
</Paper> </Paper>
</Stack> </Stack>
</> </>

View File

@ -239,16 +239,14 @@ export default function BuildDetail() {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}> <DetailsImage
<DetailsImage appRole={UserRoles.part}
appRole={UserRoles.part} apiPath={ApiEndpoints.part_list}
apiPath={ApiEndpoints.part_list} src={build.part_detail?.image ?? build.part_detail?.thumbnail}
src={build.part_detail?.image ?? build.part_detail?.thumbnail} pk={build.part}
pk={build.part} />
/> <Grid.Col span={{ base: 12, sm: 8 }}>
</Grid.Col>
<Grid.Col span={8}>
<DetailsTable fields={tl} item={build} /> <DetailsTable fields={tl} item={build} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>

View File

@ -145,22 +145,20 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}> <DetailsImage
<DetailsImage appRole={UserRoles.purchase_order}
appRole={UserRoles.purchase_order} apiPath={apiUrl(ApiEndpoints.company_list, company.pk)}
apiPath={apiUrl(ApiEndpoints.company_list, company.pk)} src={company.image}
src={company.image} pk={company.pk}
pk={company.pk} refresh={refreshInstance}
refresh={refreshInstance} imageActions={{
imageActions={{ uploadFile: true,
uploadFile: true, downloadImage: true,
downloadImage: true, deleteFile: true
deleteFile: true }}
}} />
/> <Grid.Col span={{ base: 12, sm: 8 }}>
</Grid.Col>
<Grid.Col span={8}>
<DetailsTable item={company} fields={tl} /> <DetailsTable item={company} fields={tl} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>

View File

@ -133,19 +133,17 @@ export default function ManufacturerPartDetail() {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}> <DetailsImage
<DetailsImage appRole={UserRoles.part}
appRole={UserRoles.part} src={manufacturerPart?.part_detail?.image}
src={manufacturerPart?.part_detail?.image} apiPath={apiUrl(
apiPath={apiUrl( ApiEndpoints.part_list,
ApiEndpoints.part_list, manufacturerPart?.part_detail?.pk
manufacturerPart?.part_detail?.pk )}
)} pk={manufacturerPart?.part_detail?.pk}
pk={manufacturerPart?.part_detail?.pk} />
/> <Grid.Col span={{ base: 12, sm: 8 }}>
</Grid.Col>
<Grid.Col span={8}>
<DetailsTable title={t`Part Details`} fields={tl} item={data} /> <DetailsTable title={t`Part Details`} fields={tl} item={data} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>

View File

@ -217,18 +217,16 @@ export default function SupplierPartDetail() {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}> <DetailsImage
<DetailsImage appRole={UserRoles.part}
appRole={UserRoles.part} src={supplierPart?.part_detail?.image}
src={supplierPart?.part_detail?.image} apiPath={apiUrl(
apiPath={apiUrl( ApiEndpoints.part_list,
ApiEndpoints.part_list, supplierPart?.part_detail?.pk
supplierPart?.part_detail?.pk )}
)} pk={supplierPart?.part_detail?.pk}
pk={supplierPart?.part_detail?.pk} />
/>
</Grid.Col>
<Grid.Col span={8}> <Grid.Col span={8}>
<DetailsTable title={t`Part Details`} fields={tl} item={data} /> <DetailsTable title={t`Part Details`} fields={tl} item={data} />
</Grid.Col> </Grid.Col>

View File

@ -5,7 +5,6 @@ import {
Grid, Grid,
Loader, Loader,
Skeleton, Skeleton,
Space,
Stack, Stack,
Text Text
} from '@mantine/core'; } from '@mantine/core';
@ -439,23 +438,21 @@ export default function PartDetail() {
return part ? ( return part ? (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}> <DetailsImage
<DetailsImage appRole={UserRoles.part}
appRole={UserRoles.part} imageActions={{
imageActions={{ selectExisting: true,
selectExisting: true, downloadImage: true,
downloadImage: true, uploadFile: true,
uploadFile: true, deleteFile: true
deleteFile: true }}
}} src={part.image}
src={part.image} apiPath={apiUrl(ApiEndpoints.part_list, part.pk)}
apiPath={apiUrl(ApiEndpoints.part_list, part.pk)} refresh={refreshInstance}
refresh={refreshInstance} pk={part.pk}
pk={part.pk} />
/> <Grid.Col span={{ base: 12, sm: 8 }}>
</Grid.Col>
<Grid.Col span={8}>
<DetailsTable fields={tl} item={data} /> <DetailsTable fields={tl} item={data} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>
@ -1040,9 +1037,7 @@ export default function PartDetail() {
}} }}
/> />
</Stack> </Stack>
) : ( ) : null
<Space />
)
} }
/> />
<PanelGroup <PanelGroup

View File

@ -263,16 +263,14 @@ export default function PurchaseOrderDetail() {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}> <DetailsImage
<DetailsImage appRole={UserRoles.purchase_order}
appRole={UserRoles.purchase_order} apiPath={ApiEndpoints.company_list}
apiPath={ApiEndpoints.company_list} src={order.supplier_detail?.image}
src={order.supplier_detail?.image} pk={order.supplier}
pk={order.supplier} />
/> <Grid.Col span={{ base: 12, sm: 8 }}>
</Grid.Col>
<Grid.Col span={8}>
<DetailsTable fields={tl} item={order} /> <DetailsTable fields={tl} item={order} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>

View File

@ -233,16 +233,14 @@ export default function ReturnOrderDetail() {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}> <DetailsImage
<DetailsImage appRole={UserRoles.purchase_order}
appRole={UserRoles.purchase_order} apiPath={ApiEndpoints.company_list}
apiPath={ApiEndpoints.company_list} src={order.customer_detail?.image}
src={order.customer_detail?.image} pk={order.customer}
pk={order.customer} />
/> <Grid.Col span={{ base: 12, sm: 8 }}>
</Grid.Col>
<Grid.Col span={8}>
<DetailsTable fields={tl} item={order} /> <DetailsTable fields={tl} item={order} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>

View File

@ -243,16 +243,14 @@ export default function SalesOrderDetail() {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}> <DetailsImage
<DetailsImage appRole={UserRoles.purchase_order}
appRole={UserRoles.purchase_order} apiPath={ApiEndpoints.company_list}
apiPath={ApiEndpoints.company_list} src={order.customer_detail?.image}
src={order.customer_detail?.image} pk={order.customer}
pk={order.customer} />
/> <Grid.Col span={{ base: 12, sm: 8 }}>
</Grid.Col>
<Grid.Col span={8}>
<DetailsTable fields={tl} item={order} /> <DetailsTable fields={tl} item={order} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>

View File

@ -175,22 +175,20 @@ export default function SalesOrderShipmentDetail() {
return ( return (
<> <>
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}> <DetailsImage
<DetailsImage appRole={UserRoles.sales_order}
appRole={UserRoles.sales_order} apiPath={ApiEndpoints.company_list}
apiPath={ApiEndpoints.company_list} src={customer?.image}
src={customer?.image} pk={customer?.pk}
pk={customer?.pk} imageActions={{
imageActions={{ selectExisting: false,
selectExisting: false, downloadImage: false,
downloadImage: false, uploadFile: false,
uploadFile: false, deleteFile: false
deleteFile: false }}
}} />
/> <Grid.Col span={{ base: 12, sm: 8 }}>
</Grid.Col>
<Grid.Col span={8}>
<DetailsTable fields={tl} item={data} /> <DetailsTable fields={tl} item={data} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>

View File

@ -346,19 +346,16 @@ export default function StockDetail() {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
<Grid> <Grid grow>
<Grid.Col span={4}> <DetailsImage
<DetailsImage appRole={UserRoles.part}
appRole={UserRoles.part} apiPath={ApiEndpoints.part_list}
apiPath={ApiEndpoints.part_list} src={
src={ stockitem.part_detail?.image ?? stockitem?.part_detail?.thumbnail
stockitem.part_detail?.image ?? }
stockitem?.part_detail?.thumbnail pk={stockitem.part}
} />
pk={stockitem.part} <Grid.Col span={{ base: 12, sm: 8 }}>
/>
</Grid.Col>
<Grid.Col span={8}>
<DetailsTable fields={tl} item={data} /> <DetailsTable fields={tl} item={data} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>