🔧 Library page table pagination

This commit is contained in:
2022-10-27 23:06:40 -07:00
parent 63e96bf96e
commit ff5ce10e17
8 changed files with 309 additions and 180 deletions

View File

@@ -411,10 +411,18 @@ pre {
} }
// Library // Library
.sticky { .sticky {
position: sticky;
top: 57px;
z-index: 2;
background: #fffffc; background: #fffffc;
position: sticky;
top: 107px;
z-index: 5;
.title {
background: #fffffc;
position: relative;
top: -56px;
z-index: 99999;
padding-top: 20px;
}
} }
.library { .library {
@@ -423,7 +431,7 @@ pre {
width: 100%; width: 100%;
thead { thead {
position: sticky; position: sticky;
top: 146px; top: 176px;
z-index: 1; z-index: 1;
background: #fffffc; background: #fffffc;
min-height: 130px; min-height: 130px;

View File

@@ -20,7 +20,7 @@ export const PullList = ({ issues }: PullListProps): ReactElement => {
useEffect(() => { useEffect(() => {
dispatch( dispatch(
getWeeklyPullList({ getWeeklyPullList({
startDate: "2022-9-9", startDate: "2022-10-9",
pageSize: "15", pageSize: "15",
currentPage: "1", currentPage: "1",
}), }),

View File

@@ -1,22 +1,169 @@
import React, { useMemo, ReactElement, useCallback } from "react"; import React, { useMemo, ReactElement, useCallback, useEffect } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import T2Table from "../shared/T2Table";
import { isEmpty, isNil, isUndefined } from "lodash"; import { isEmpty, isNil, isUndefined } from "lodash";
import MetadataPanel from "../shared/MetadataPanel"; import MetadataPanel from "../shared/MetadataPanel";
import SearchBar from "./SearchBar"; import SearchBar from "./SearchBar";
import { useDispatch } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { searchIssue } from "../../actions/fileops.actions"; import { searchIssue } from "../../actions/fileops.actions";
import ellipsize from "ellipsize"; import ellipsize from "ellipsize";
interface IComicBookLibraryProps {
data: {
searchResults: any;
};
}
export const Library = (data: IComicBookLibraryProps): ReactElement => { import {
const { searchResults } = data.data; ColumnDef,
flexRender,
getCoreRowModel,
getFilteredRowModel,
useReactTable,
PaginationState,
} from "@tanstack/react-table";
export const Library = (): ReactElement => {
const searchResults = useSelector(
(state: RootState) => state.fileOps.libraryComics,
);
const searchError = useSelector(
(state: RootState) => state.fileOps.librarySearchError,
);
const dispatch = useDispatch();
useEffect(() => {
dispatch(
searchIssue(
{
query: {},
},
{
pagination: {
size: 25,
from: 0,
},
type: "all",
trigger: "libraryPage"
},
),
);
}, []);
const T2Table = (tableOptions): ReactElement => {
const { columns, totalPages, rowClickHandler } =
tableOptions;
// pagination methods
const goToNextPage = useCallback((pageIndex, pageSize) => {
dispatch(
searchIssue(
{
query: {},
},
{
pagination: {
size: pageSize,
from: pageSize * pageIndex + 1,
},
type: "all",
trigger: "libraryPage",
},
),
);
}, []);
const goToPreviousPage = useCallback((pageIndex, pageSize) => {
let from = 0;
if (pageIndex === 2) {
from = (pageIndex - 1) * pageSize + 2 - 27;
} else {
from = (pageIndex - 1) * pageSize + 2 - 26;
}
dispatch(
searchIssue(
{
query: {},
},
{
pagination: {
size: pageSize,
from,
},
type: "all",
trigger: "libraryPage"
},
),
);
}, []);
const table = useReactTable({
data: searchResults.hits.hits,
columns,
manualPagination: true,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
pageCount: totalPages,
// getPaginationRowModel: getPaginationRowModel(),
debugTable: true,
});
return (
<>
<table className="table is-hoverable">
<thead>
{table.getHeaderGroups().map((headerGroup, idx) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header, idx) => (
<th
key={header.id}
colSpan={header.colSpan}
>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row, idx) => {
return (
<tr
key={row.id}
onClick={() => rowClickHandler(row)}
>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
);
})}
</tbody>
</table>
{/* pagination control */}
<nav className="pagination">
{table.getState().pagination.pageIndex + 1}
<div className="field has-addons">
<p className="control">
<div className="button" onClick={() => goToNextPage(table.getState().pagination.pageIndex + 1, 25)}> Next Page </div>
</p>
<p className="control">
<div className="button" > Previous Page</div>
</p>
</div>
</nav>
</>
);
};
// programatically navigate to comic detail // programatically navigate to comic detail
const navigate = useNavigate(); const navigate = useNavigate();
@@ -65,124 +212,81 @@ export const Library = (data: IComicBookLibraryProps): ReactElement => {
const WantedStatus = ({ value }) => { const WantedStatus = ({ value }) => {
return !value ? <span className="tag is-info is-light">Wanted</span> : null; return !value ? <span className="tag is-info is-light">Wanted</span> : null;
}; };
const columns = [ const columns = [
{ {
header: "Comic Metadata", header: "Comic Metadata",
footer: 1, footer: 1,
columns: [ columns: [
{ {
header: "File Details", header: "File Details",
id: "fileDetails", id: "fileDetails",
minWidth: 400, minWidth: 400,
accessorKey: "_source", accessorKey: "_source",
cell: info => { cell: info => {
return <MetadataPanel data={info.getValue()} />; return <MetadataPanel data={info.getValue()} />;
},
}, },
{ },
header: "ComicInfo.xml", {
accessorKey: "_source.sourcedMetadata.comicInfo", header: "ComicInfo.xml",
align: "center", accessorKey: "_source.sourcedMetadata.comicInfo",
minWidth: 250, align: "center",
cell: info => minWidth: 250,
!isEmpty(info.getValue()) ? ( cell: info =>
<ComicInfoXML data={info.getValue()} /> !isEmpty(info.getValue()) ? (
) : ( <ComicInfoXML data={info.getValue()} />
<span className="tag">No ComicInfo.xml</span> ) : (
), <span className="tag">No ComicInfo.xml</span>
),
},
],
},
{
header: "Additional Metadata",
columns: [
{
header: "Publisher",
accessorKey:
"_source.sourcedMetadata.comicvine.volumeInformation",
cell: info => {
return (
!isNil(info.getValue()) && (
<h6 className="is-size-7 has-text-weight-bold">
{info.getValue().publisher.name}
</h6>
)
);
}, },
], },
}, {
{ header: "Something",
header: "Additional Metadata", accessorKey: "_source.acquisition.source.wanted",
columns: [ cell: info => {
{ !isUndefined(info.getValue()) ?
header: "Publisher",
accessorKey:
"_source.sourcedMetadata.comicvine.volumeInformation",
cell: info => {
return (
!isNil(info.getValue()) && (
<h6 className="is-size-7 has-text-weight-bold">
{ info.getValue().publisher.name }
</h6>
)
);
},
},
{
header: "Something",
accessorKey: "_source.acquisition.source.wanted",
cell: info => {
!isUndefined(info.getValue()) ?
<WantedStatus value={info.getValue().toString()} /> : "Nothing"; <WantedStatus value={info.getValue().toString()} /> : "Nothing";
},
}, },
], },
} ],
] }
]
// ImportStatus.propTypes = { // ImportStatus.propTypes = {
// value: PropTypes.bool.isRequired, // value: PropTypes.bool.isRequired,
// }; // };
const dispatch = useDispatch();
const goToNextPage = useCallback((pageIndex, pageSize) => {
dispatch(
searchIssue(
{
query: {},
},
{
pagination: {
size: pageSize,
from: pageSize * pageIndex + 1,
},
type: "all",
},
),
);
}, []);
const goToPreviousPage = useCallback((pageIndex, pageSize) => {
let from = 0;
if (pageIndex === 2) {
from = (pageIndex - 1) * pageSize + 2 - 27;
} else {
from = (pageIndex - 1) * pageSize + 2 - 26;
}
dispatch(
searchIssue(
{
query: {},
},
{
pagination: {
size: pageSize,
from,
},
type: "all",
},
),
);
}, []);
return ( return (
<section className="container"> <section className="container">
<div className="section"> <div className="section">
<h1 className="title">Library</h1> <div className="sticky"><h1 className="title">Library</h1></div>
{/* Search bar */} {/* Search bar */}
<SearchBar /> <SearchBar />
{!isUndefined(searchResults) && ( {!isUndefined(searchResults.hits) && (
<div> <div>
<div className="library"> <div className="library">
<T2Table <T2Table
rowData={searchResults.hits.hits}
totalPages={searchResults.hits.total.value} totalPages={searchResults.hits.total.value}
columns={columns} columns={columns}
paginationHandlers={{
nextPage: goToNextPage,
previousPage: goToPreviousPage,
}}
rowClickHandler={navigateToComicDetail} rowClickHandler={navigateToComicDetail}
/> />
{/* pagination controls */} {/* pagination controls */}

View File

@@ -5,59 +5,58 @@ import { searchIssue } from "../../actions/fileops.actions";
import { Library } from "./Library"; import { Library } from "./Library";
const LibraryContainer = (): ReactElement => { const LibraryContainer = (): ReactElement => {
const dispatch = useDispatch(); // const dispatch = useDispatch();
useEffect(() => { // useEffect(() => {
dispatch( // dispatch(
searchIssue( // searchIssue(
{ // {
query: {}, // query: {},
}, // },
{ // {
pagination: { // pagination: {
size: 25, // size: 25,
from: 0, // from: 0,
}, // },
type: "all", // type: "all",
trigger: "libraryPage" // trigger: "libraryPage"
}, // },
), // ),
); // );
}, []); // }, []);
const searchResults = useSelector( // const searchResults = useSelector(
(state: RootState) => state.fileOps.libraryComics, // (state: RootState) => state.fileOps.libraryComics,
); // );
const searchError = useSelector( // const searchError = useSelector(
(state: RootState) => state.fileOps.librarySearchError, // (state: RootState) => state.fileOps.librarySearchError,
); // );
return !isEmpty(searchResults) ? ( return (
<Library data={{ searchResults }} /> <Library />)
) : ( // : (
<div className="container"> // <div className="container">
<section className="section is-small"> // <section className="section is-small">
<div className="columns"> // <div className="columns">
<div className="column is-two-thirds"> // <div className="column is-two-thirds">
<article className="message is-link"> // <article className="message is-link">
<div className="message-body"> // <div className="message-body">
No comics were found in the library, Elasticsearch reports no // No comics were found in the library, and Elasticsearch doesn't have any
indices. Try importing a few comics into the library and come // indices. Try resetting the library from <code>Settings > Flush DB & Temporary Folders</code> and then import your library again.
back. // </div>
</div> // </article>
</article> // <pre>
<pre> // {!isUndefined(searchError.data) &&
{!isUndefined(searchError.data) && // JSON.stringify(
JSON.stringify( // searchError.data.meta.body.error.root_cause,
searchError.data.meta.body.error.root_cause, // null,
null, // 4,
4, // )}
)} // </pre>
</pre> // </div>
</div> // </div>
</div> // </section>
</section> // </div>
</div> // );
);
}; };
export default LibraryContainer; export default LibraryContainer;

View File

@@ -26,14 +26,14 @@ export const SearchBar = (): ReactElement => {
); );
}, []); }, []);
return ( return (
<div className="box sticky"> <div className="sticky">
<Form <Form
onSubmit={handleSubmit} onSubmit={handleSubmit}
initialValues={{}} initialValues={{}}
render={({ handleSubmit, form, submitting, pristine, values }) => ( render={({ handleSubmit, form, submitting, pristine, values }) => (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="field is-grouped"> <div className="field is-grouped">
<div className="control is-expanded search"> <div className="control search">
<Field name="search"> <Field name="search">
{({ input, meta }) => { {({ input, meta }) => {
return ( return (

View File

@@ -77,14 +77,9 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
</div> </div>
)} )}
</div> </div>
</div> </div>
<div className="control">
<div className="tags has-addons">
<span className="tag is-light is-success">Path</span>
<span className="tag is-warning is-light">{rawFileDetails.containedIn}</span>
</div>
</div>
</dd> </dd>
</dl> </dl>
), ),
@@ -218,7 +213,7 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
orientation={"vertical"} orientation={"vertical"}
hasDetails={false} hasDetails={false}
imageStyle={props.imageStyle} imageStyle={props.imageStyle}
// cardContainerStyle={{ maxWidth: 200 }} // cardContainerStyle={{ maxWidth: 200 }}
/> />
</div> </div>
<div className="column">{metadataPanel.content()}</div> <div className="column">{metadataPanel.content()}</div>

View File

@@ -4,22 +4,39 @@ import {
ColumnDef, ColumnDef,
flexRender, flexRender,
getCoreRowModel, getCoreRowModel,
getFilteredRowModel,
useReactTable, useReactTable,
PaginationState,
} from "@tanstack/react-table"; } from "@tanstack/react-table";
export const T2Table = (tableOptions): ReactElement => { export const T2Table = (tableOptions): ReactElement => {
const { rowData, columns, paginationHandlers, totalPages, rowClickHandler } = const { rowData, columns, paginationHandlers: { nextPage, previousPage }, totalPages, rowClickHandler } =
tableOptions; tableOptions;
const [isPageSizeDropdownCollapsed, collapsePageSizeDropdown] =
useState(false);
const togglePageSizeDropdown = () =>
collapsePageSizeDropdown(!isPageSizeDropdownCollapsed);
const table = useReactTable({ const table = useReactTable({
data: rowData, data: rowData,
columns, columns,
manualPagination: true,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
pageCount: totalPages,
// getPaginationRowModel: getPaginationRowModel(),
}); });
const [{ pageIndex, pageSize }, setPagination] =
React.useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
})
const pagination = React.useMemo(
() => ({
pageIndex,
pageSize,
}),
[pageIndex, pageSize]
)
return ( return (
<> <>
<table className="table is-hoverable"> <table className="table is-hoverable">
@@ -62,6 +79,12 @@ export const T2Table = (tableOptions): ReactElement => {
</table> </table>
{/* pagination control */} {/* pagination control */}
<nav className="pagination">
{table.getState().pagination.pageIndex + 1}
<div className="button" onClick={() => table.nextPage()}> Next Page </div>
<div className="button" onClick={previousPage}> Previous Page</div>
</nav>
</> </>
); );

View File

@@ -188,6 +188,7 @@ function fileOpsReducer(state = initialState, action) {
} }
case SS_SEARCH_RESULTS_FETCHED: { case SS_SEARCH_RESULTS_FETCHED: {
console.log(action.data);
return { return {
...state, ...state,
libraryComics: action.data, libraryComics: action.data,
@@ -196,7 +197,6 @@ function fileOpsReducer(state = initialState, action) {
} }
case SS_SEARCH_RESULTS_FETCHED_SPECIAL: { case SS_SEARCH_RESULTS_FETCHED_SPECIAL: {
const foo = []; const foo = [];
console.log(action.data.hits)
if (!isUndefined(action.data.hits)) { if (!isUndefined(action.data.hits)) {
map(action.data.hits.hits, ({ _source }) => { map(action.data.hits.hits, ({ _source }) => {
foo.push(_source); foo.push(_source);