2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 03:26:45 +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.Td
style={{
width: '50',
maxWidth: '50'
}}
>
<InvenTreeIcon icon={field.icon ?? (field.name as InvenTreeIconType)} />
</Table.Td>
<Table.Td style={{ maxWidth: '65%', lineBreak: 'auto' }}>
<Table.Td style={{ minWidth: 75, lineBreak: 'auto', flex: 2 }}>
<Text>{field.label}</Text>
</Table.Td>
<Table.Td style={{ lineBreak: 'anywhere' }}>
<Table.Td style={{ lineBreak: 'anywhere', minWidth: 100, flex: 10 }}>
<FieldType field_data={field} field_value={fieldValue} />
</Table.Td>
<Table.Td style={{ width: '50' }}>
@ -409,7 +408,11 @@ export function DetailsTable({
title?: string;
}>) {
return (
<Paper p='xs' withBorder radius='xs'>
<Paper
p='xs'
withBorder
style={{ overflowX: 'hidden', width: '100%', minWidth: 200 }}
>
<Stack gap='xs'>
{title && <StylishText size='lg'>{title}</StylishText>}
<Table striped verticalSpacing={5} horizontalSpacing='sm'>

View File

@ -2,6 +2,7 @@ import { Trans, t } from '@lingui/macro';
import {
AspectRatio,
Button,
Grid,
Group,
Image,
Overlay,
@ -421,31 +422,39 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
return (
<>
{downloadImage.modal}
<AspectRatio ref={ref} maw={IMAGE_DIMENSION} ratio={1} pos='relative'>
<>
<ApiImage
src={img}
mah={IMAGE_DIMENSION}
maw={IMAGE_DIMENSION}
onClick={expandImage}
/>
{permissions.hasChangeRole(props.appRole) &&
hasOverlay &&
hovered && (
<Overlay color='black' opacity={0.8} onClick={expandImage}>
<ImageActionButtons
visible={hovered}
actions={props.imageActions}
apiPath={props.apiPath}
hasImage={!!props.src}
pk={props.pk}
setImage={setAndRefresh}
downloadImage={downloadImage.open}
/>
</Overlay>
)}
</>
</AspectRatio>
<Grid.Col span={{ base: 12, sm: 4 }}>
<AspectRatio
ref={ref}
maw={IMAGE_DIMENSION}
ratio={1}
pos='relative'
visibleFrom='xs'
>
<>
<ApiImage
src={img}
mah={IMAGE_DIMENSION}
maw={IMAGE_DIMENSION}
onClick={expandImage}
/>
{permissions.hasChangeRole(props.appRole) &&
hasOverlay &&
hovered && (
<Overlay color='black' opacity={0.8} onClick={expandImage}>
<ImageActionButtons
visible={hovered}
actions={props.imageActions}
apiPath={props.apiPath}
hasImage={!!props.src}
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 { useElementSize } from '@mantine/hooks';
import type React from 'react';
import { useMemo } from 'react';
export function ItemDetailsGrid(props: React.PropsWithChildren<{}>) {
const { ref, width } = useElementSize();
const cols = useMemo(() => (width > 700 ? 2 : 1), [width]);
return (
<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}
</SimpleGrid>
</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 { Fragment, type ReactNode } from 'react';
import { Fragment, type ReactNode, useMemo } from 'react';
import { shortenString } from '../../functions/tables';
import { ApiImage } from '../images/ApiImage';
import { StylishText } from '../items/StylishText';
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 (
<>
<PageTitle title={title} />
<PageTitle title={pageTitleString} />
<Stack gap='xs'>
{breadcrumbs && breadcrumbs.length > 0 && (
<BreadcrumbList
@ -62,8 +95,19 @@ export function PageDetail({
/>
)}
<Paper p='xs' radius='xs' shadow='xs'>
<Stack gap='xs'>
<Group justify='space-between' wrap='nowrap'>
<Group
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'>
{imageUrl && (
<ApiImage
@ -72,6 +116,7 @@ export function PageDetail({
miw={42}
mah={42}
maw={42}
visibleFrom='sm'
/>
)}
<Stack gap='xs'>
@ -79,30 +124,33 @@ export function PageDetail({
{subtitle && (
<Group gap='xs'>
{icon}
<Text size='sm' truncate>
{subtitle}
</Text>
<Text size='sm'>{description}</Text>
</Group>
)}
</Stack>
</Group>
<Space />
{detail}
<Group justify='right' gap='xs' wrap='nowrap'>
{badges?.map((badge, idx) => (
<Fragment key={idx}>{badge}</Fragment>
))}
</Group>
<Space />
{actions && (
<Group gap={5} justify='right'>
{actions.map((action, idx) => (
<Fragment key={idx}>{action}</Fragment>
{detail && <div>{detail}</div>}
{badges && (
<Group
justify='center'
gap='xs'
align='flex-start'
wrap='nowrap'
>
{badges?.map((badge, idx) => (
<Fragment key={idx}>{badge}</Fragment>
))}
</Group>
)}
</Group>
</Stack>
</SimpleGrid>
{actions && (
<Group gap={5} justify='right' wrap='nowrap' align='flex-start'>
{actions.map((action, idx) => (
<Fragment key={idx}>{action}</Fragment>
))}
</Group>
)}
</Group>
</Paper>
</Stack>
</>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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