mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +00:00
PartThumbTable updates (#9094)
- Change columns based on viewport width - Use debounced value - Enable pagination - Fix pagination on backend API
This commit is contained in:
parent
a3ffc01d88
commit
480536a023
@ -522,6 +522,9 @@ class PartThumbs(ListAPI):
|
|||||||
|
|
||||||
data = serializer.data
|
data = serializer.data
|
||||||
|
|
||||||
|
if page is not None:
|
||||||
|
return self.get_paginated_response(data)
|
||||||
|
else:
|
||||||
return Response(data)
|
return Response(data)
|
||||||
|
|
||||||
filter_backends = [InvenTreeSearchFilter]
|
filter_backends = [InvenTreeSearchFilter]
|
||||||
|
@ -305,7 +305,7 @@ function ImageActionButtons({
|
|||||||
|
|
||||||
modals.open({
|
modals.open({
|
||||||
title: <StylishText size='xl'>{t`Select Image`}</StylishText>,
|
title: <StylishText size='xl'>{t`Select Image`}</StylishText>,
|
||||||
size: 'xxl',
|
size: '80%',
|
||||||
children: <PartThumbTable pk={pk} setImage={setImage} />
|
children: <PartThumbTable pk={pk} setImage={setImage} />
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Divider,
|
Divider,
|
||||||
Group,
|
Group,
|
||||||
|
Pagination,
|
||||||
Paper,
|
Paper,
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
@ -11,12 +12,13 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
TextInput
|
TextInput
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useHover } from '@mantine/hooks';
|
import { useDebouncedValue, useHover } from '@mantine/hooks';
|
||||||
import { modals } from '@mantine/modals';
|
import { modals } from '@mantine/modals';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { Suspense, useEffect, useState } from 'react';
|
import { Suspense, useState } from 'react';
|
||||||
|
|
||||||
|
import { IconX } from '@tabler/icons-react';
|
||||||
import { api } from '../../App';
|
import { api } from '../../App';
|
||||||
import { Thumbnail } from '../../components/images/Thumbnail';
|
import { Thumbnail } from '../../components/images/Thumbnail';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
@ -27,9 +29,6 @@ import { apiUrl } from '../../states/ApiState';
|
|||||||
*/
|
*/
|
||||||
export type ThumbTableProps = {
|
export type ThumbTableProps = {
|
||||||
pk: string;
|
pk: string;
|
||||||
limit?: number;
|
|
||||||
offset?: number;
|
|
||||||
search?: string;
|
|
||||||
setImage: (image: string) => void;
|
setImage: (image: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -122,36 +121,41 @@ async function setNewImage(
|
|||||||
/**
|
/**
|
||||||
* Renders a "table" of thumbnails
|
* Renders a "table" of thumbnails
|
||||||
*/
|
*/
|
||||||
export function PartThumbTable({
|
export function PartThumbTable({ pk, setImage }: Readonly<ThumbTableProps>) {
|
||||||
limit = 24,
|
const limit = 24;
|
||||||
offset = 0,
|
|
||||||
search = '',
|
|
||||||
pk,
|
|
||||||
setImage
|
|
||||||
}: Readonly<ThumbTableProps>) {
|
|
||||||
const [thumbImage, setThumbImage] = useState<string | null>(null);
|
const [thumbImage, setThumbImage] = useState<string | null>(null);
|
||||||
const [filterInput, setFilterInput] = useState<string>('');
|
const [filterInput, setFilterInput] = useState<string>('');
|
||||||
const [filterQuery, setFilterQuery] = useState<string>(search);
|
|
||||||
|
const [page, setPage] = useState<number>(1);
|
||||||
|
const [totalPages, setTotalPages] = useState<number>(1);
|
||||||
|
|
||||||
// Keep search filters from updating while user is typing
|
// Keep search filters from updating while user is typing
|
||||||
useEffect(() => {
|
const [searchText] = useDebouncedValue(filterInput, 500);
|
||||||
const timeoutId = setTimeout(() => setFilterQuery(filterInput), 500);
|
|
||||||
return () => clearTimeout(timeoutId);
|
|
||||||
}, [filterInput]);
|
|
||||||
|
|
||||||
// Fetch thumbnails from API
|
// Fetch thumbnails from API
|
||||||
const thumbQuery = useQuery({
|
const thumbQuery = useQuery({
|
||||||
queryKey: [
|
queryKey: [ApiEndpoints.part_thumbs_list, page, searchText],
|
||||||
ApiEndpoints.part_thumbs_list,
|
|
||||||
{ limit: limit, offset: offset, search: filterQuery }
|
|
||||||
],
|
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return api.get(apiUrl(ApiEndpoints.part_thumbs_list), {
|
const offset = Math.max(0, page - 1) * limit;
|
||||||
|
|
||||||
|
return api
|
||||||
|
.get(apiUrl(ApiEndpoints.part_thumbs_list), {
|
||||||
params: {
|
params: {
|
||||||
offset: offset,
|
offset: offset,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
search: filterQuery
|
search: searchText
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
const records = response?.data?.count ?? 1;
|
||||||
|
setTotalPages(Math.ceil(records / limit));
|
||||||
|
return response.data?.results ?? response.data;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setTotalPages(1);
|
||||||
|
setPage(1);
|
||||||
|
return [];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -161,18 +165,20 @@ export function PartThumbTable({
|
|||||||
<Suspense>
|
<Suspense>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Paper p='sm'>
|
<Paper p='sm'>
|
||||||
<SimpleGrid cols={8}>
|
<SimpleGrid
|
||||||
|
cols={{ base: 2, '450px': 3, '600px': 4, '900px': 6 }}
|
||||||
|
type='container'
|
||||||
|
spacing='xs'
|
||||||
|
>
|
||||||
{!thumbQuery.isFetching
|
{!thumbQuery.isFetching
|
||||||
? thumbQuery.data?.data.map(
|
? thumbQuery?.data.map((data: ImageElement, index: number) => (
|
||||||
(data: ImageElement, index: number) => (
|
|
||||||
<PartThumbComponent
|
<PartThumbComponent
|
||||||
element={data}
|
element={data}
|
||||||
key={index}
|
key={index}
|
||||||
selected={thumbImage}
|
selected={thumbImage}
|
||||||
selectImage={setThumbImage}
|
selectImage={setThumbImage}
|
||||||
/>
|
/>
|
||||||
)
|
))
|
||||||
)
|
|
||||||
: [...Array(limit)].map((elem, idx) => (
|
: [...Array(limit)].map((elem, idx) => (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
height={150}
|
height={150}
|
||||||
@ -188,13 +194,28 @@ export function PartThumbTable({
|
|||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
<Paper p='sm'>
|
<Paper p='sm'>
|
||||||
<Group justify='space-between'>
|
<Group justify='space-between' gap='xs'>
|
||||||
|
<Group justify='left' gap='xs'>
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder={t`Search...`}
|
placeholder={t`Search...`}
|
||||||
|
value={filterInput}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setFilterInput(event.currentTarget.value);
|
setFilterInput(event.currentTarget.value);
|
||||||
}}
|
}}
|
||||||
|
rightSection={
|
||||||
|
<IconX
|
||||||
|
size='1rem'
|
||||||
|
color='red'
|
||||||
|
onClick={() => setFilterInput('')}
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Pagination
|
||||||
|
total={totalPages}
|
||||||
|
value={page}
|
||||||
|
onChange={(value) => setPage(value)}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
<Button
|
<Button
|
||||||
disabled={!thumbImage}
|
disabled={!thumbImage}
|
||||||
onClick={() => setNewImage(thumbImage, pk, setImage)}
|
onClick={() => setNewImage(thumbImage, pk, setImage)}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user