🏗️ Continued refactoring of PullList, Volumes etc.
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
"@dnd-kit/sortable": "^7.0.2",
|
"@dnd-kit/sortable": "^7.0.2",
|
||||||
"@dnd-kit/utilities": "^3.2.1",
|
"@dnd-kit/utilities": "^3.2.1",
|
||||||
"@fortawesome/fontawesome-free": "^6.3.0",
|
"@fortawesome/fontawesome-free": "^6.3.0",
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
"@tanstack/react-query": "^5.0.5",
|
"@tanstack/react-query": "^5.0.5",
|
||||||
"@tanstack/react-table": "^8.9.3",
|
"@tanstack/react-table": "^8.9.3",
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
"filename-parser": "^1.0.2",
|
"filename-parser": "^1.0.2",
|
||||||
"final-form": "^4.20.2",
|
"final-form": "^4.20.2",
|
||||||
"final-form-arrays": "^3.0.2",
|
"final-form-arrays": "^3.0.2",
|
||||||
|
"focus-trap-react": "^10.2.3",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
"html-to-text": "^8.1.0",
|
"html-to-text": "^8.1.0",
|
||||||
"immer": "^10.0.3",
|
"immer": "^10.0.3",
|
||||||
@@ -49,13 +51,14 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-collapsible": "^2.9.0",
|
"react-collapsible": "^2.9.0",
|
||||||
"react-comic-viewer": "^0.4.0",
|
"react-comic-viewer": "^0.4.0",
|
||||||
"react-day-picker": "^8.6.0",
|
"react-day-picker": "^8.10.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-fast-compare": "^3.2.0",
|
"react-fast-compare": "^3.2.0",
|
||||||
"react-final-form": "^6.5.9",
|
"react-final-form": "^6.5.9",
|
||||||
"react-final-form-arrays": "^3.1.4",
|
"react-final-form-arrays": "^3.1.4",
|
||||||
"react-loader-spinner": "^4.0.0",
|
"react-loader-spinner": "^4.0.0",
|
||||||
"react-modal": "^3.15.1",
|
"react-modal": "^3.15.1",
|
||||||
|
"react-popper": "^2.3.0",
|
||||||
"react-router": "^6.9.0",
|
"react-router": "^6.9.0",
|
||||||
"react-router-dom": "^6.9.0",
|
"react-router-dom": "^6.9.0",
|
||||||
"react-select": "^5.8.0",
|
"react-select": "^5.8.0",
|
||||||
|
|||||||
@@ -6,14 +6,9 @@ import { VolumeGroups } from "./VolumeGroups";
|
|||||||
import { LibraryStatistics } from "./LibraryStatistics";
|
import { LibraryStatistics } from "./LibraryStatistics";
|
||||||
import { PullList } from "./PullList";
|
import { PullList } from "./PullList";
|
||||||
import { getLibraryStatistics } from "../../actions/comicinfo.actions";
|
import { getLibraryStatistics } from "../../actions/comicinfo.actions";
|
||||||
import { isEmpty, isNil, isUndefined } from "lodash";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import Header from "../shared/Header";
|
|
||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import {
|
import { LIBRARY_SERVICE_BASE_URI } from "../../constants/endpoints";
|
||||||
LIBRARY_SERVICE_BASE_URI,
|
|
||||||
LIBRARY_SERVICE_HOST,
|
|
||||||
} from "../../constants/endpoints";
|
|
||||||
|
|
||||||
export const Dashboard = (): ReactElement => {
|
export const Dashboard = (): ReactElement => {
|
||||||
const { data: recentComics } = useQuery({
|
const { data: recentComics } = useQuery({
|
||||||
@@ -65,9 +60,7 @@ export const Dashboard = (): ReactElement => {
|
|||||||
// );
|
// );
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto max-w-full">
|
<div className="container mx-auto max-w-full">
|
||||||
<h1>Dashboard</h1>
|
|
||||||
<PullList />
|
<PullList />
|
||||||
|
|
||||||
{recentComics && <RecentlyImported comics={recentComics?.data.docs} />}
|
{recentComics && <RecentlyImported comics={recentComics?.data.docs} />}
|
||||||
{/* Wanted comics */}
|
{/* Wanted comics */}
|
||||||
<WantedComicsList comics={wantedComics?.data?.docs} />
|
<WantedComicsList comics={wantedComics?.data?.docs} />
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import React, { ReactElement } from "react";
|
import React, { ReactElement, useState } from "react";
|
||||||
import { map } from "lodash";
|
import { map } from "lodash";
|
||||||
import Card from "../shared/Carda";
|
import Card from "../shared/Carda";
|
||||||
import Header from "../shared/Header";
|
import Header from "../shared/Header";
|
||||||
import { importToDB } from "../../actions/fileops.actions";
|
import { importToDB } from "../../actions/fileops.actions";
|
||||||
import ellipsize from "ellipsize";
|
import ellipsize from "ellipsize";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import rateLimiter from "axios-rate-limit";
|
import rateLimiter from "axios-rate-limit";
|
||||||
import { setupCache } from "axios-cache-interceptor";
|
import { setupCache } from "axios-cache-interceptor";
|
||||||
@@ -12,6 +13,8 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import "keen-slider/keen-slider.min.css";
|
import "keen-slider/keen-slider.min.css";
|
||||||
import { useKeenSlider } from "keen-slider/react";
|
import { useKeenSlider } from "keen-slider/react";
|
||||||
import { COMICVINE_SERVICE_URI } from "../../constants/endpoints";
|
import { COMICVINE_SERVICE_URI } from "../../constants/endpoints";
|
||||||
|
import { Field, Form } from "react-final-form";
|
||||||
|
import DatePickerDialog from "../shared/DatePicker";
|
||||||
|
|
||||||
type PullListProps = {
|
type PullListProps = {
|
||||||
issues: any;
|
issues: any;
|
||||||
@@ -24,6 +27,13 @@ const http = rateLimiter(axios.create(), {
|
|||||||
});
|
});
|
||||||
const cachedAxios = setupCache(axios);
|
const cachedAxios = setupCache(axios);
|
||||||
export const PullList = (): ReactElement => {
|
export const PullList = (): ReactElement => {
|
||||||
|
// datepicker
|
||||||
|
const [selected, setSelected] = useState<Date>();
|
||||||
|
let footer = <p>Please pick a day.</p>;
|
||||||
|
if (selected) {
|
||||||
|
footer = <p>You picked {format(selected, "PP")}.</p>;
|
||||||
|
}
|
||||||
|
|
||||||
// keen slider
|
// keen slider
|
||||||
const [sliderRef, instanceRef] = useKeenSlider(
|
const [sliderRef, instanceRef] = useKeenSlider(
|
||||||
{
|
{
|
||||||
@@ -47,15 +57,15 @@ export const PullList = (): ReactElement => {
|
|||||||
data: pullList,
|
data: pullList,
|
||||||
isSuccess,
|
isSuccess,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
isError,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryFn: async () =>
|
queryFn: async (): any =>
|
||||||
await cachedAxios(`${COMICVINE_SERVICE_URI}/getWeeklyPullList`, {
|
await cachedAxios(`${COMICVINE_SERVICE_URI}/getWeeklyPullList`, {
|
||||||
method: "get",
|
method: "get",
|
||||||
params: { startDate: "2024-2-15", pageSize: "15", currentPage: "1" },
|
params: { startDate: "2024-2-20", pageSize: "15", currentPage: "1" },
|
||||||
}),
|
}),
|
||||||
queryKey: ["pullList"],
|
queryKey: ["pullList"],
|
||||||
});
|
});
|
||||||
console.log(pullList?.data.result);
|
|
||||||
const addToLibrary = (sourceName: string, locgMetadata) =>
|
const addToLibrary = (sourceName: string, locgMetadata) =>
|
||||||
importToDB(sourceName, { locg: locgMetadata });
|
importToDB(sourceName, { locg: locgMetadata });
|
||||||
|
|
||||||
@@ -65,6 +75,7 @@ export const PullList = (): ReactElement => {
|
|||||||
const previous = () => {
|
const previous = () => {
|
||||||
// sliderRef.slickPrev();
|
// sliderRef.slickPrev();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="content">
|
<div className="content">
|
||||||
@@ -73,24 +84,30 @@ export const PullList = (): ReactElement => {
|
|||||||
subHeaderContent="Pull List aggregated for the week from League Of Comic Geeks"
|
subHeaderContent="Pull List aggregated for the week from League Of Comic Geeks"
|
||||||
iconClassNames="fa-solid fa-binoculars mr-2"
|
iconClassNames="fa-solid fa-binoculars mr-2"
|
||||||
/>
|
/>
|
||||||
<div className="field is-grouped">
|
<div className="flex flex-row gap-5 mb-5">
|
||||||
{/* select week */}
|
{/* select week */}
|
||||||
<div className="control">
|
<div className="flex flex-row gap-4 my-3">
|
||||||
<div className="select is-small">
|
<Form
|
||||||
<select>
|
onSubmit={() => {}}
|
||||||
<option>Select Week</option>
|
render={({ handleSubmit }) => (
|
||||||
<option>With options</option>
|
<form>
|
||||||
</select>
|
{/* week selection for pull list */}
|
||||||
</div>
|
|
||||||
</div>
|
<DatePickerDialog />
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
{/* See all pull list issues */}
|
{/* See all pull list issues */}
|
||||||
<div className="control">
|
|
||||||
<Link to={"/pull-list/all/"}>
|
<Link to={"/pull-list/all/"}>
|
||||||
<button className="button is-small">View all issues</button>
|
<button className="flex space-x-1 sm:flex-row sm:items-center rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-3 py-1 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500">
|
||||||
|
View all issues
|
||||||
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{isSuccess && !isLoading && (
|
{isSuccess && !isLoading && (
|
||||||
<div ref={sliderRef} className="keen-slider flex flex-row">
|
<div ref={sliderRef} className="keen-slider flex flex-row">
|
||||||
@@ -103,21 +120,30 @@ export const PullList = (): ReactElement => {
|
|||||||
hasDetails
|
hasDetails
|
||||||
title={ellipsize(issue.name, 25)}
|
title={ellipsize(issue.name, 25)}
|
||||||
>
|
>
|
||||||
<div className="px-1 py-1">
|
<div className="px-1">
|
||||||
<span className="text-xs ">{issue.publisher}</span>
|
<span className="inline-flex mb-2 items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-1 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
|
{issue.publisher}
|
||||||
|
</span>
|
||||||
|
<div className="flex flex-row justify-end">
|
||||||
<button
|
<button
|
||||||
className=""
|
className="flex space-x-1 mb-2 sm:mt-0 sm:flex-row sm:items-center rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-2 py-1 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
||||||
onClick={() => addToLibrary("locg", issue)}
|
onClick={() => addToLibrary("locg", issue)}
|
||||||
>
|
>
|
||||||
|
<i className="icon-[solar--add-square-bold-duotone] w-5 h-5 mr-2"></i>{" "}
|
||||||
Want
|
Want
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{isLoading ? <div>Loading...</div> : null}
|
||||||
|
{isError ? (
|
||||||
|
<div>An error occurred while retrieving the pull list.</div>
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export const VolumeGroups = (props): ReactElement => {
|
|||||||
headerContent="Volumes"
|
headerContent="Volumes"
|
||||||
subHeaderContent="Based on ComicVine Volume information"
|
subHeaderContent="Based on ComicVine Volume information"
|
||||||
iconClassNames="fa-solid fa-binoculars mr-2"
|
iconClassNames="fa-solid fa-binoculars mr-2"
|
||||||
|
link={"/volumes"}
|
||||||
/>
|
/>
|
||||||
<div className="grid grid-cols-5 gap-6 mt-3">
|
<div className="grid grid-cols-5 gap-6 mt-3">
|
||||||
{map(deduplicatedGroups, (data) => {
|
{map(deduplicatedGroups, (data) => {
|
||||||
@@ -36,7 +37,7 @@ export const VolumeGroups = (props): ReactElement => {
|
|||||||
{ellipsize(data.volumes.name, 48)}
|
{ellipsize(data.volumes.name, 48)}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
{/* issue count */}
|
||||||
<span className="inline-flex mt-1 items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-600 dark:bg-slate-400">
|
<span className="inline-flex mt-1 items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-600 dark:bg-slate-400">
|
||||||
<span className="pr-1 pt-1">
|
<span className="pr-1 pt-1">
|
||||||
<i className="icon-[solar--documents-minimalistic-bold-duotone] w-5 h-5"></i>
|
<i className="icon-[solar--documents-minimalistic-bold-duotone] w-5 h-5"></i>
|
||||||
|
|||||||
@@ -15,15 +15,14 @@ export const WantedComicsList = ({
|
|||||||
comics,
|
comics,
|
||||||
}: WantedComicsListProps): ReactElement => {
|
}: WantedComicsListProps): ReactElement => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const navigateToWantedComics = (row) => {
|
|
||||||
navigate(`/wanted/all`);
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header
|
<Header
|
||||||
headerContent="Wanted Comics"
|
headerContent="Wanted Comics"
|
||||||
subHeaderContent="Comics marked as wanted from various sources"
|
subHeaderContent="Comics marked as wanted from various sources"
|
||||||
iconClassNames="fa-solid fa-binoculars mr-2"
|
iconClassNames="fa-solid fa-binoculars mr-2"
|
||||||
|
link={"/wanted"}
|
||||||
/>
|
/>
|
||||||
<div className="grid grid-cols-5 gap-6 mt-3">
|
<div className="grid grid-cols-5 gap-6 mt-3">
|
||||||
{map(
|
{map(
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ export const Library = (): ReactElement => {
|
|||||||
<div>
|
<div>
|
||||||
<section>
|
<section>
|
||||||
<header className="bg-slate-200 dark:bg-slate-500">
|
<header className="bg-slate-200 dark:bg-slate-500">
|
||||||
<div className="px-2 py-2 sm:px-6 sm:py-8 lg:px-8 lg:py-4">
|
<div className="mx-auto max-w-screen-xl px-2 py-2 sm:px-6 sm:py-8 lg:px-8 lg:py-4">
|
||||||
<div className="sm:flex sm:items-center sm:justify-between">
|
<div className="sm:flex sm:items-center sm:justify-between">
|
||||||
<div className="text-center sm:text-left">
|
<div className="text-center sm:text-left">
|
||||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white sm:text-3xl">
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-white sm:text-3xl">
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [comicVineMetadata, setComicVineMetadata] = useState({});
|
const [comicVineMetadata, setComicVineMetadata] = useState({});
|
||||||
const getCVSearchResults = (searchQuery) => {
|
const getCVSearchResults = (searchQuery) => {
|
||||||
console.log(searchQuery);
|
|
||||||
setSearchQuery(searchQuery.search);
|
setSearchQuery(searchQuery.search);
|
||||||
// queryClient.invalidateQueries({ queryKey: ["comicvineSearchResults"] });
|
// queryClient.invalidateQueries({ queryKey: ["comicvineSearchResults"] });
|
||||||
};
|
};
|
||||||
@@ -84,7 +83,6 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
enabled: !isNil(comicVineMetadata.comicData),
|
enabled: !isNil(comicVineMetadata.comicData),
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(comicVineMetadata);
|
|
||||||
const addToLibrary = (sourceName: string, comicData) =>
|
const addToLibrary = (sourceName: string, comicData) =>
|
||||||
setComicVineMetadata({ sourceName, comicData });
|
setComicVineMetadata({ sourceName, comicData });
|
||||||
|
|
||||||
@@ -93,11 +91,24 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
<section className="container">
|
<section>
|
||||||
<div className="">
|
<header className="bg-slate-200 dark:bg-slate-500">
|
||||||
<h1 className="">Search</h1>
|
<div className="px-2 py-2 sm:px-6 sm:py-8 lg:px-8 lg:py-4">
|
||||||
|
<div className="sm:flex sm:items-center sm:justify-between">
|
||||||
|
<div className="text-center sm:text-left">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-white sm:text-3xl">
|
||||||
|
Search
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="mt-1.5 text-sm text-gray-500 dark:text-white">
|
||||||
|
Browse your comic book collection.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div className="mx-auto max-w-screen-sm px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
|
||||||
<Form
|
<Form
|
||||||
onSubmit={getCVSearchResults}
|
onSubmit={getCVSearchResults}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
@@ -105,30 +116,39 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
}}
|
}}
|
||||||
render={({ handleSubmit, form, submitting, pristine, values }) => (
|
render={({ handleSubmit, form, submitting, pristine, values }) => (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div>
|
<div className="flex flex-row w-full">
|
||||||
|
<div className="flex flex-row bg-slate-300 dark:bg-slate-500 rounded-l-lg p-2 min-w-full">
|
||||||
|
<div className="w-10 text-gray-400">
|
||||||
|
<i className="icon-[solar--magnifer-bold-duotone] h-7 w-7" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<Field name="search">
|
<Field name="search">
|
||||||
{({ input, meta }) => {
|
{({ input, meta }) => {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
{...input}
|
{...input}
|
||||||
className="input main-search-bar is-large"
|
className="bg-slate-300 dark:bg-slate-500 outline-none text-lg text-gray-700 w-full"
|
||||||
placeholder="Type an issue/volume name"
|
placeholder="Type an issue/volume name"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
<div className="column">
|
|
||||||
<button type="submit" className="button is-medium">
|
<button
|
||||||
|
className="sm:mt-0 rounded-r-lg border border-green-400 dark:border-green-200 bg-green-200 px-3 py-1 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
Search
|
Search
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
{!isNil(comicVineSearchResults?.data.results) &&
|
{!isNil(comicVineSearchResults?.data.results) &&
|
||||||
!isEmpty(comicVineSearchResults?.data.results) ? (
|
!isEmpty(comicVineSearchResults?.data.results) ? (
|
||||||
<div className="">
|
<div className="mx-auto max-w-screen-xl px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
|
||||||
{comicVineSearchResults.data.results.map((result) => {
|
{comicVineSearchResults.data.results.map((result) => {
|
||||||
return isSuccess ? (
|
return isSuccess ? (
|
||||||
<div key={result.id} className="mb-5">
|
<div key={result.id} className="mb-5">
|
||||||
@@ -161,9 +181,7 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
|
|
||||||
<div className="control">
|
<div className="control">
|
||||||
<div className="tags has-addons">
|
<div className="tags has-addons">
|
||||||
<span className="tag is-warning">
|
<span className="tag is-warning">{result.id}</span>
|
||||||
{result.id}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -199,22 +217,28 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<article className="message is-dark is-half">
|
<div className="mx-auto mx-auto max-w-screen-md px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
|
||||||
<div className="message-body">
|
<article
|
||||||
<p className="mb-2">
|
role="alert"
|
||||||
<span className="tag is-medium is-info is-light">
|
className="mt-4 rounded-lg max-w-screen-md border-s-4 border-blue-500 bg-blue-50 p-4 dark:border-s-4 dark:border-blue-600 dark:bg-blue-300 dark:text-slate-600"
|
||||||
Search the ComicVine database
|
>
|
||||||
</span>
|
<div>
|
||||||
|
<p> Search the ComicVine database</p>
|
||||||
|
<p>
|
||||||
|
Note that you need an instance of AirDC++ already running to
|
||||||
|
use this form to connect to it.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
Search and add issues, series and trade paperbacks to your
|
Search and add issues, series and trade paperbacks to your
|
||||||
library. Then, download them using the configured AirDC++ or
|
library. Then, download them using the configured AirDC++ or
|
||||||
torrent clients.
|
torrent clients.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,80 +4,63 @@ import Card from "../shared/Carda";
|
|||||||
import T2Table from "../shared/T2Table";
|
import T2Table from "../shared/T2Table";
|
||||||
import ellipsize from "ellipsize";
|
import ellipsize from "ellipsize";
|
||||||
import { convert } from "html-to-text";
|
import { convert } from "html-to-text";
|
||||||
import { isUndefined } from "lodash";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import axios from "axios";
|
||||||
|
import { SEARCH_SERVICE_BASE_URI } from "../../constants/endpoints";
|
||||||
|
|
||||||
export const Volumes = (props): ReactElement => {
|
export const Volumes = (props): ReactElement => {
|
||||||
// const volumes = useSelector((state: RootState) => state.fileOps.volumes);
|
// const volumes = useSelector((state: RootState) => state.fileOps.volumes);
|
||||||
useEffect(() => {
|
const {
|
||||||
// dispatch(
|
data: volumes,
|
||||||
// searchIssue(
|
isSuccess,
|
||||||
// {
|
isError,
|
||||||
// query: {},
|
isLoading,
|
||||||
// },
|
} = useQuery({
|
||||||
// {
|
queryFn: async () =>
|
||||||
// pagination: {
|
await axios({
|
||||||
// size: 25,
|
url: `${SEARCH_SERVICE_BASE_URI}/searchIssue`,
|
||||||
// from: 0,
|
method: "POST",
|
||||||
// },
|
data: {
|
||||||
// type: "volumes",
|
query: {},
|
||||||
// trigger: "volumesPage",
|
pagination: {
|
||||||
// },
|
size: 25,
|
||||||
// ),
|
from: 0,
|
||||||
// );
|
},
|
||||||
}, []);
|
type: "volumes",
|
||||||
|
trigger: "volumesPage",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
queryKey: ["volumes"],
|
||||||
|
});
|
||||||
|
console.log(volumes);
|
||||||
const columnData = useMemo(
|
const columnData = useMemo(
|
||||||
() => [
|
(): any => [
|
||||||
{
|
{
|
||||||
header: "Volume Details",
|
header: "Volume Details",
|
||||||
id: "volumeDetails",
|
id: "volumeDetails",
|
||||||
minWidth: 450,
|
minWidth: 450,
|
||||||
accessorKey: "_source",
|
accessorKey: "_source",
|
||||||
cell: (row) => {
|
cell: (row): any => {
|
||||||
const foo = row.getValue();
|
const foo = row.getValue();
|
||||||
return (
|
return (
|
||||||
<div className="columns">
|
<div className="flex flex-row gap-3 mt-5">
|
||||||
<div className="column">
|
|
||||||
<div className="comic-detail issue-metadata">
|
|
||||||
<dl>
|
|
||||||
<dd>
|
|
||||||
<div className="columns mt-2">
|
|
||||||
<div className="">
|
|
||||||
<Card
|
<Card
|
||||||
imageUrl={
|
imageUrl={
|
||||||
foo.sourcedMetadata.comicvine.volumeInformation
|
foo.sourcedMetadata.comicvine.volumeInformation.image
|
||||||
.image.thumb_url
|
.small_url
|
||||||
}
|
}
|
||||||
orientation={"vertical"}
|
orientation={"cover-only"}
|
||||||
hasDetails={false}
|
hasDetails={false}
|
||||||
// cardContainerStyle={{ maxWidth: 200 }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
<div className="dark:bg-[#647587] bg-slate-200 p-3 rounded-lg h-fit">
|
||||||
<div className="column">
|
<span className="text-xl mb-1">
|
||||||
<dl>
|
{foo.sourcedMetadata.comicvine.volumeInformation.name}
|
||||||
<dt>
|
|
||||||
<h6 className="name has-text-weight-medium mb-1">
|
|
||||||
{
|
|
||||||
foo.sourcedMetadata.comicvine
|
|
||||||
.volumeInformation.name
|
|
||||||
}
|
|
||||||
</h6>
|
|
||||||
</dt>
|
|
||||||
<dd className="is-size-7">
|
|
||||||
published by{" "}
|
|
||||||
<span className="has-text-weight-semibold">
|
|
||||||
{
|
|
||||||
foo.sourcedMetadata.comicvine
|
|
||||||
.volumeInformation.publisher.name
|
|
||||||
}
|
|
||||||
</span>
|
</span>
|
||||||
</dd>
|
<p>
|
||||||
|
|
||||||
<dd className="is-size-7">
|
|
||||||
<span>
|
|
||||||
{ellipsize(
|
{ellipsize(
|
||||||
convert(
|
convert(
|
||||||
foo.sourcedMetadata.comicvine
|
foo.sourcedMetadata.comicvine.volumeInformation
|
||||||
.volumeInformation.description,
|
.description,
|
||||||
{
|
{
|
||||||
baseElements: {
|
baseElements: {
|
||||||
selectors: ["p"],
|
selectors: ["p"],
|
||||||
@@ -86,42 +69,17 @@ export const Volumes = (props): ReactElement => {
|
|||||||
),
|
),
|
||||||
120,
|
120,
|
||||||
)}
|
)}
|
||||||
</span>
|
</p>
|
||||||
</dd>
|
|
||||||
|
|
||||||
<dd className="is-size-7 mt-2">
|
|
||||||
<div className="field is-grouped is-grouped-multiline">
|
|
||||||
<div className="control">
|
|
||||||
<span className="tags">
|
|
||||||
<span className="tag is-success is-light has-text-weight-semibold">
|
|
||||||
Total Issues
|
|
||||||
</span>
|
|
||||||
<span className="tag is-success is-light">
|
|
||||||
{
|
|
||||||
foo.sourcedMetadata.comicvine
|
|
||||||
.volumeInformation.count_of_issues
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: "Download Status",
|
header: "Other Details",
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
header: "Files",
|
header: "Downloads",
|
||||||
accessorKey: "_source.acquisition.directconnect",
|
accessorKey: "_source.acquisition.directconnect",
|
||||||
align: "right",
|
align: "right",
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
@@ -142,12 +100,34 @@ export const Volumes = (props): ReactElement => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: "Type",
|
header: "Publisher",
|
||||||
id: "Air",
|
accessorKey: "_source.sourcedMetadata.comicvine.volumeInformation",
|
||||||
|
cell: (props): any => {
|
||||||
|
const row = props.getValue();
|
||||||
|
return <div className="mt-5 text-md">{row.publisher.name}</div>;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: "Type",
|
header: "Issue Count",
|
||||||
id: "dcc",
|
accessorKey:
|
||||||
|
"_source.sourcedMetadata.comicvine.volumeInformation.count_of_issues",
|
||||||
|
cell: (props): any => {
|
||||||
|
const row = props.getValue();
|
||||||
|
return (
|
||||||
|
<div className="mt-5">
|
||||||
|
{/* issue count */}
|
||||||
|
<span className="inline-flex items-center bg-slate-50 text-slate-800 font-medium px-2.5 py-0.5 rounded-md dark:text-slate-600 dark:bg-slate-400">
|
||||||
|
<span className="pr-1 pt-1">
|
||||||
|
<i className="icon-[solar--documents-minimalistic-bold-duotone] w-6 h-6"></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="text-lg text-slate-500 dark:text-slate-900">
|
||||||
|
{row}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -155,17 +135,29 @@ export const Volumes = (props): ReactElement => {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<section className="container">
|
<div>
|
||||||
<div className="section">
|
<section className="">
|
||||||
<div className="header-area">
|
<header className="bg-slate-200 dark:bg-slate-500">
|
||||||
<h1 className="title">Volumes</h1>
|
<div className="mx-auto max-w-screen-xl px-2 py-2 sm:px-6 sm:py-8 lg:px-8 lg:py-4">
|
||||||
|
<div className="sm:flex sm:items-center sm:justify-between">
|
||||||
|
<div className="text-center sm:text-left">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-white sm:text-3xl">
|
||||||
|
Volumes
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="mt-1.5 text-sm text-gray-500 dark:text-white">
|
||||||
|
Browse your collection of volumes.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{!isUndefined(volumes.hits) && (
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
{isSuccess ? (
|
||||||
<div>
|
<div>
|
||||||
<div className="library">
|
<div className="library">
|
||||||
<T2Table
|
<T2Table
|
||||||
sourceData={volumes?.hits}
|
sourceData={volumes?.data.hits.hits}
|
||||||
totalPages={volumes.hits.length}
|
totalPages={volumes?.data.hits.hits.length}
|
||||||
paginationHandlers={{
|
paginationHandlers={{
|
||||||
nextPage: () => {},
|
nextPage: () => {},
|
||||||
previousPage: () => {},
|
previousPage: () => {},
|
||||||
@@ -174,9 +166,13 @@ export const Volumes = (props): ReactElement => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : null}
|
||||||
</div>
|
{isError ? (
|
||||||
|
<div>An error was encountered while retrieving volumes</div>
|
||||||
|
) : null}
|
||||||
|
{isLoading ? <>Loading...</> : null}
|
||||||
</section>
|
</section>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,36 @@
|
|||||||
import React, { ReactElement, useCallback, useEffect, useMemo } from "react";
|
import React, { ReactElement, useCallback, useEffect, useMemo } from "react";
|
||||||
import { searchIssue } from "../../actions/fileops.actions";
|
|
||||||
import SearchBar from "../Library/SearchBar";
|
import SearchBar from "../Library/SearchBar";
|
||||||
import T2Table from "../shared/T2Table";
|
import T2Table from "../shared/T2Table";
|
||||||
import { isEmpty, isUndefined } from "lodash";
|
|
||||||
import MetadataPanel from "../shared/MetadataPanel";
|
import MetadataPanel from "../shared/MetadataPanel";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import axios from "axios";
|
||||||
|
import { SEARCH_SERVICE_BASE_URI } from "../../constants/endpoints";
|
||||||
|
|
||||||
export const WantedComics = (props): ReactElement => {
|
export const WantedComics = (props): ReactElement => {
|
||||||
// const wantedComics = useSelector(
|
const {
|
||||||
// (state: RootState) => state.fileOps.wantedComics,
|
data: wantedComics,
|
||||||
// );
|
isSuccess,
|
||||||
useEffect(() => {
|
isError,
|
||||||
// dispatch(
|
isLoading,
|
||||||
// searchIssue(
|
} = useQuery({
|
||||||
// {
|
queryFn: async () =>
|
||||||
// query: {},
|
await axios({
|
||||||
// },
|
url: `${SEARCH_SERVICE_BASE_URI}/searchIssue`,
|
||||||
// {
|
method: "POST",
|
||||||
// pagination: {
|
data: {
|
||||||
// size: 25,
|
query: {},
|
||||||
// from: 0,
|
|
||||||
// },
|
|
||||||
// type: "wanted",
|
|
||||||
// trigger: "wantedComicsPage"
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
pagination: {
|
||||||
|
size: 25,
|
||||||
|
from: 0,
|
||||||
|
},
|
||||||
|
type: "wanted",
|
||||||
|
trigger: "wantedComicsPage",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
queryKey: ["wantedComics"],
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
const columnData = [
|
const columnData = [
|
||||||
{
|
{
|
||||||
header: "Comic Information",
|
header: "Comic Information",
|
||||||
@@ -36,7 +40,10 @@ export const WantedComics = (props): ReactElement => {
|
|||||||
id: "comicDetails",
|
id: "comicDetails",
|
||||||
minWidth: 350,
|
minWidth: 350,
|
||||||
accessorFn: (data) => data,
|
accessorFn: (data) => data,
|
||||||
cell: (value) => <MetadataPanel data={value.getValue()} />,
|
cell: (value) => {
|
||||||
|
const row = value.getValue()._source;
|
||||||
|
return row && <MetadataPanel data={row} />;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -45,8 +52,8 @@ export const WantedComics = (props): ReactElement => {
|
|||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
header: "Files",
|
header: "Files",
|
||||||
accessorKey: "acquisition",
|
|
||||||
align: "right",
|
align: "right",
|
||||||
|
accessorKey: "_source.acquisition",
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const {
|
const {
|
||||||
directconnect: { downloads },
|
directconnect: { downloads },
|
||||||
@@ -69,7 +76,7 @@ export const WantedComics = (props): ReactElement => {
|
|||||||
{
|
{
|
||||||
header: "Download Details",
|
header: "Download Details",
|
||||||
id: "downloadDetails",
|
id: "downloadDetails",
|
||||||
accessorKey: "acquisition",
|
accessorKey: "_source.acquisition",
|
||||||
cell: (data) => (
|
cell: (data) => (
|
||||||
<ol>
|
<ol>
|
||||||
{data.getValue().directconnect.downloads.map((download, idx) => {
|
{data.getValue().directconnect.downloads.map((download, idx) => {
|
||||||
@@ -98,23 +105,23 @@ export const WantedComics = (props): ReactElement => {
|
|||||||
* @returns void
|
* @returns void
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
const nextPage = useCallback((pageIndex: number, pageSize: number) => {
|
// const nextPage = useCallback((pageIndex: number, pageSize: number) => {
|
||||||
dispatch(
|
// dispatch(
|
||||||
searchIssue(
|
// searchIssue(
|
||||||
{
|
// {
|
||||||
query: {},
|
// query: {},
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
pagination: {
|
// pagination: {
|
||||||
size: pageSize,
|
// size: pageSize,
|
||||||
from: pageSize * pageIndex + 1,
|
// from: pageSize * pageIndex + 1,
|
||||||
},
|
// },
|
||||||
type: "wanted",
|
// type: "wanted",
|
||||||
trigger: "wantedComicsPage",
|
// trigger: "wantedComicsPage",
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pagination control that fetches the previous x (pageSize) items
|
* Pagination control that fetches the previous x (pageSize) items
|
||||||
@@ -123,55 +130,71 @@ export const WantedComics = (props): ReactElement => {
|
|||||||
* @param {number} pageSize
|
* @param {number} pageSize
|
||||||
* @returns void
|
* @returns void
|
||||||
**/
|
**/
|
||||||
const previousPage = useCallback((pageIndex: number, pageSize: number) => {
|
// const previousPage = useCallback((pageIndex: number, pageSize: number) => {
|
||||||
let from = 0;
|
// let from = 0;
|
||||||
if (pageIndex === 2) {
|
// if (pageIndex === 2) {
|
||||||
from = (pageIndex - 1) * pageSize + 2 - 17;
|
// from = (pageIndex - 1) * pageSize + 2 - 17;
|
||||||
} else {
|
// } else {
|
||||||
from = (pageIndex - 1) * pageSize + 2 - 16;
|
// from = (pageIndex - 1) * pageSize + 2 - 16;
|
||||||
}
|
// }
|
||||||
dispatch(
|
// dispatch(
|
||||||
searchIssue(
|
// searchIssue(
|
||||||
{
|
// {
|
||||||
query: {},
|
// query: {},
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
pagination: {
|
// pagination: {
|
||||||
size: pageSize,
|
// size: pageSize,
|
||||||
from,
|
// from,
|
||||||
},
|
// },
|
||||||
type: "wanted",
|
// type: "wanted",
|
||||||
trigger: "wantedComicsPage",
|
// trigger: "wantedComicsPage",
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="container">
|
<div className="">
|
||||||
<div className="section">
|
<section className="">
|
||||||
<div className="header-area">
|
<header className="bg-slate-200 dark:bg-slate-500">
|
||||||
<h1 className="title">Wanted Comics</h1>
|
<div className="mx-auto max-w-screen-xl px-2 py-2 sm:px-6 sm:py-8 lg:px-8 lg:py-4">
|
||||||
|
<div className="sm:flex sm:items-center sm:justify-between">
|
||||||
|
<div className="text-center sm:text-left">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-white sm:text-3xl">
|
||||||
|
Wanted Comics
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="mt-1.5 text-sm text-gray-500 dark:text-white">
|
||||||
|
Browse through comics you marked as "wanted."
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{!isEmpty(wantedComics) && (
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
{isSuccess ? (
|
||||||
<div>
|
<div>
|
||||||
<div className="library">
|
<div className="library">
|
||||||
<T2Table
|
<T2Table
|
||||||
sourceData={wantedComics}
|
sourceData={wantedComics?.data.hits.hits}
|
||||||
totalPages={wantedComics.length}
|
totalPages={wantedComics?.data.hits.hits.length}
|
||||||
columns={columnData}
|
columns={columnData}
|
||||||
paginationHandlers={{
|
paginationHandlers={{
|
||||||
nextPage: nextPage,
|
nextPage: () => {},
|
||||||
previousPage: previousPage,
|
previousPage: () => {},
|
||||||
}}
|
}}
|
||||||
// rowClickHandler={navigateToComicDetail}
|
// rowClickHandler={navigateToComicDetail}
|
||||||
/>
|
/>
|
||||||
{/* pagination controls */}
|
{/* pagination controls */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : null}
|
||||||
</div>
|
{isLoading ? <div>Loading...</div> : null}
|
||||||
|
{isError ? (
|
||||||
|
<div>An error occurred while retrieving the pull list.</div>
|
||||||
|
) : null}
|
||||||
</section>
|
</section>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
103
src/client/components/shared/DatePicker.tsx
Normal file
103
src/client/components/shared/DatePicker.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import React, { ChangeEventHandler, useRef, useState } from "react";
|
||||||
|
|
||||||
|
import { format, isValid, parse } from "date-fns";
|
||||||
|
import FocusTrap from "focus-trap-react";
|
||||||
|
import { DayPicker, SelectSingleEventHandler } from "react-day-picker";
|
||||||
|
import { usePopper } from "react-popper";
|
||||||
|
|
||||||
|
export default function DatePickerDialog() {
|
||||||
|
const [selected, setSelected] = useState<Date>();
|
||||||
|
const [inputValue, setInputValue] = useState<string>("");
|
||||||
|
const [isPopperOpen, setIsPopperOpen] = useState(false);
|
||||||
|
|
||||||
|
const popperRef = useRef<HTMLDivElement>(null);
|
||||||
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const popper = usePopper(popperRef.current, popperElement, {
|
||||||
|
placement: "bottom-start",
|
||||||
|
});
|
||||||
|
|
||||||
|
const closePopper = () => {
|
||||||
|
setIsPopperOpen(false);
|
||||||
|
buttonRef?.current?.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||||
|
setInputValue(e.currentTarget.value);
|
||||||
|
const date = parse(e.currentTarget.value, "y-MM-dd", new Date());
|
||||||
|
if (isValid(date)) {
|
||||||
|
setSelected(date);
|
||||||
|
} else {
|
||||||
|
setSelected(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleButtonClick = () => {
|
||||||
|
setIsPopperOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDaySelect: SelectSingleEventHandler = (date) => {
|
||||||
|
setSelected(date);
|
||||||
|
if (date) {
|
||||||
|
setInputValue(format(date, "y-MM-dd"));
|
||||||
|
closePopper();
|
||||||
|
} else {
|
||||||
|
setInputValue("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div ref={popperRef}>
|
||||||
|
<input
|
||||||
|
size={12}
|
||||||
|
type="text"
|
||||||
|
placeholder={format(new Date(), "y-MM-dd")}
|
||||||
|
value={inputValue}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
ref={buttonRef}
|
||||||
|
type="button"
|
||||||
|
aria-label="Pick a date"
|
||||||
|
onClick={handleButtonClick}
|
||||||
|
>
|
||||||
|
Pick a date
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{isPopperOpen && (
|
||||||
|
<FocusTrap
|
||||||
|
active
|
||||||
|
focusTrapOptions={{
|
||||||
|
initialFocus: false,
|
||||||
|
allowOutsideClick: true,
|
||||||
|
clickOutsideDeactivates: true,
|
||||||
|
onDeactivate: closePopper,
|
||||||
|
fallbackFocus: buttonRef.current || undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
tabIndex={-1}
|
||||||
|
style={popper.styles.popper}
|
||||||
|
className="bg-slate-200 p-2 rounded-lg z-50"
|
||||||
|
{...popper.attributes.popper}
|
||||||
|
ref={setPopperElement}
|
||||||
|
role="dialog"
|
||||||
|
aria-label="DayPicker calendar"
|
||||||
|
>
|
||||||
|
<DayPicker
|
||||||
|
initialFocus={isPopperOpen}
|
||||||
|
mode="single"
|
||||||
|
defaultMonth={selected}
|
||||||
|
selected={selected}
|
||||||
|
onSelect={handleDaySelect}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</FocusTrap>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,20 +1,29 @@
|
|||||||
import React, { ReactElement } from "react";
|
import React, { ReactElement } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
type IHeaderProps = {
|
type IHeaderProps = {
|
||||||
headerContent: string;
|
headerContent: string;
|
||||||
subHeaderContent: string;
|
subHeaderContent: string;
|
||||||
iconClassNames: string;
|
iconClassNames: string;
|
||||||
|
link?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Header = (props: IHeaderProps): ReactElement => {
|
export const Header = (props: IHeaderProps): ReactElement => {
|
||||||
return (
|
return (
|
||||||
<div className="mt-7">
|
<div className="mt-7">
|
||||||
<div className="">
|
<div className="">
|
||||||
<a className="" onClick={() => {}}>
|
{props.link ? (
|
||||||
|
<Link to={props.link}>
|
||||||
<span className="text-xl">
|
<span className="text-xl">
|
||||||
<i className=""></i> {props.headerContent}
|
<span className="underline">
|
||||||
|
{props.headerContent}{" "}
|
||||||
|
<i className="icon-[solar--arrow-right-up-outline] w-4 h-4" />
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</span>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<div className="text-xl">{props.headerContent}</div>
|
||||||
|
)}
|
||||||
<p className="">{props.subHeaderContent}</p>
|
<p className="">{props.subHeaderContent}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ interface IMetadatPanelProps {
|
|||||||
containerStyle: any;
|
containerStyle: any;
|
||||||
}
|
}
|
||||||
export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
|
export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
|
||||||
|
console.log(props);
|
||||||
const {
|
const {
|
||||||
rawFileDetails,
|
rawFileDetails,
|
||||||
inferredMetadata,
|
inferredMetadata,
|
||||||
@@ -114,7 +115,6 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dd className="is-size-7">
|
<dd className="is-size-7">
|
||||||
<span>
|
<span>
|
||||||
{ellipsize(
|
{ellipsize(
|
||||||
@@ -127,42 +127,13 @@ export const MetadataPanel = (props: IMetadatPanelProps): ReactElement => {
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dd className="is-size-7 mt-2">
|
<dd className="is-size-7 mt-2">
|
||||||
<div className="field is-grouped is-grouped-multiline">
|
<span className="my-3 mx-2">
|
||||||
<div className="control">
|
|
||||||
<span className="tags">
|
|
||||||
<span
|
|
||||||
className="tag is-success is-light has-text-weight-semibold"
|
|
||||||
style={props.tagsStyle}
|
|
||||||
>
|
|
||||||
{comicvine.volumeInformation.start_year}
|
{comicvine.volumeInformation.start_year}
|
||||||
</span>
|
</span>
|
||||||
<span
|
|
||||||
className="tag is-success is-light"
|
|
||||||
style={props.tagsStyle}
|
|
||||||
>
|
|
||||||
{comicvine.volumeInformation.count_of_issues}
|
{comicvine.volumeInformation.count_of_issues}
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="control">
|
|
||||||
<div className="tags has-addons">
|
|
||||||
<span
|
|
||||||
className="tag is-primary is-light"
|
|
||||||
style={props.tagsStyle}
|
|
||||||
>
|
|
||||||
ComicVine ID
|
ComicVine ID
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="tag is-info is-light"
|
|
||||||
style={props.tagsStyle}
|
|
||||||
>
|
|
||||||
{comicvine.id}
|
{comicvine.id}
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -52,12 +52,12 @@ export const Navbar2 = (): ReactElement => {
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a
|
<Link
|
||||||
|
to="/volumes"
|
||||||
className="text-gray-500 transition hover:text-gray-500/75 dark:text-white dark:hover:text-white/75"
|
className="text-gray-500 transition hover:text-gray-500/75 dark:text-white dark:hover:text-white/75"
|
||||||
href="/"
|
|
||||||
>
|
>
|
||||||
Volumes
|
Volumes
|
||||||
</a>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
@@ -69,12 +69,12 @@ export const Navbar2 = (): ReactElement => {
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<Link
|
||||||
className="text-gray-500 transition hover:text-gray-500/75 dark:text-white dark:hover:text-white/75"
|
className="text-gray-500 transition hover:text-gray-500/75 dark:text-white dark:hover:text-white/75"
|
||||||
href="/search"
|
to="/search"
|
||||||
>
|
>
|
||||||
Comicvine Search
|
Comicvine Search
|
||||||
</a>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import Dashboard from "./components/Dashboard/Dashboard";
|
|||||||
import Search from "./components/Search/Search";
|
import Search from "./components/Search/Search";
|
||||||
import TabulatedContentContainer from "./components/Library/TabulatedContentContainer";
|
import TabulatedContentContainer from "./components/Library/TabulatedContentContainer";
|
||||||
import { ComicDetailContainer } from "./components/ComicDetail/ComicDetailContainer";
|
import { ComicDetailContainer } from "./components/ComicDetail/ComicDetailContainer";
|
||||||
|
import Volumes from "./components/Volumes/Volumes";
|
||||||
|
import WantedComics from "./components/WantedComics/WantedComics";
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
@@ -23,6 +25,7 @@ const router = createBrowserRouter([
|
|||||||
element: <App />,
|
element: <App />,
|
||||||
errorElement: <ErrorPage />,
|
errorElement: <ErrorPage />,
|
||||||
children: [
|
children: [
|
||||||
|
{ path: "/", element: <Dashboard /> },
|
||||||
{ path: "dashboard", element: <Dashboard /> },
|
{ path: "dashboard", element: <Dashboard /> },
|
||||||
{ path: "settings", element: <Settings /> },
|
{ path: "settings", element: <Settings /> },
|
||||||
{
|
{
|
||||||
@@ -35,6 +38,8 @@ const router = createBrowserRouter([
|
|||||||
},
|
},
|
||||||
{ path: "import", element: <Import path={"./comics"} /> },
|
{ path: "import", element: <Import path={"./comics"} /> },
|
||||||
{ path: "search", element: <Search /> },
|
{ path: "search", element: <Search /> },
|
||||||
|
{ path: "volumes", element: <Volumes /> },
|
||||||
|
{ path: "wanted", element: <WantedComics /> },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
39
yarn.lock
39
yarn.lock
@@ -1914,6 +1914,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
||||||
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
||||||
|
|
||||||
|
"@popperjs/core@^2.11.8":
|
||||||
|
version "2.11.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
|
||||||
|
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
||||||
|
|
||||||
"@radix-ui/number@1.0.1":
|
"@radix-ui/number@1.0.1":
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.0.1.tgz#644161a3557f46ed38a042acf4a770e826021674"
|
resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.0.1.tgz#644161a3557f46ed38a042acf4a770e826021674"
|
||||||
@@ -6012,6 +6017,21 @@ flow-parser@0.*:
|
|||||||
resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.227.0.tgz#e50b65be9dc6810438c975e816a68005fbcd5107"
|
resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.227.0.tgz#e50b65be9dc6810438c975e816a68005fbcd5107"
|
||||||
integrity sha512-nOygtGKcX/siZK/lFzpfdHEfOkfGcTW7rNroR1Zsz6T/JxSahPALXVt5qVHq/fgvMJuv096BTKbgxN3PzVBaDA==
|
integrity sha512-nOygtGKcX/siZK/lFzpfdHEfOkfGcTW7rNroR1Zsz6T/JxSahPALXVt5qVHq/fgvMJuv096BTKbgxN3PzVBaDA==
|
||||||
|
|
||||||
|
focus-trap-react@^10.2.3:
|
||||||
|
version "10.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/focus-trap-react/-/focus-trap-react-10.2.3.tgz#a5a2ea7fbb042ffa4337fde72758325ed0fb793a"
|
||||||
|
integrity sha512-YXBpFu/hIeSu6NnmV2xlXzOYxuWkoOtar9jzgp3lOmjWLWY59C/b8DtDHEAV4SPU07Nd/t+nS/SBNGkhUBFmEw==
|
||||||
|
dependencies:
|
||||||
|
focus-trap "^7.5.4"
|
||||||
|
tabbable "^6.2.0"
|
||||||
|
|
||||||
|
focus-trap@^7.5.4:
|
||||||
|
version "7.5.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-7.5.4.tgz#6c4e342fe1dae6add9c2aa332a6e7a0bbd495ba2"
|
||||||
|
integrity sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==
|
||||||
|
dependencies:
|
||||||
|
tabbable "^6.2.0"
|
||||||
|
|
||||||
follow-redirects@^1.15.4:
|
follow-redirects@^1.15.4:
|
||||||
version "1.15.5"
|
version "1.15.5"
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020"
|
||||||
@@ -8896,7 +8916,7 @@ react-confetti@^6.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tween-functions "^1.2.0"
|
tween-functions "^1.2.0"
|
||||||
|
|
||||||
react-day-picker@^8.6.0:
|
react-day-picker@^8.10.0:
|
||||||
version "8.10.0"
|
version "8.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-day-picker/-/react-day-picker-8.10.0.tgz#729c5b9564967a924213978fb9c0751884a60595"
|
resolved "https://registry.yarnpkg.com/react-day-picker/-/react-day-picker-8.10.0.tgz#729c5b9564967a924213978fb9c0751884a60595"
|
||||||
integrity sha512-mz+qeyrOM7++1NCb1ARXmkjMkzWVh2GL9YiPbRjKe0zHccvekk4HE+0MPOZOrosn8r8zTHIIeOUXTmXRqmkRmg==
|
integrity sha512-mz+qeyrOM7++1NCb1ARXmkjMkzWVh2GL9YiPbRjKe0zHccvekk4HE+0MPOZOrosn8r8zTHIIeOUXTmXRqmkRmg==
|
||||||
@@ -8939,7 +8959,7 @@ react-element-to-jsx-string@^15.0.0:
|
|||||||
is-plain-object "5.0.0"
|
is-plain-object "5.0.0"
|
||||||
react-is "18.1.0"
|
react-is "18.1.0"
|
||||||
|
|
||||||
react-fast-compare@^3.2.0:
|
react-fast-compare@^3.0.1, react-fast-compare@^3.2.0:
|
||||||
version "3.2.2"
|
version "3.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49"
|
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49"
|
||||||
integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==
|
integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==
|
||||||
@@ -9012,6 +9032,14 @@ react-modal@^3.14.3, react-modal@^3.15.1:
|
|||||||
react-lifecycles-compat "^3.0.0"
|
react-lifecycles-compat "^3.0.0"
|
||||||
warning "^4.0.3"
|
warning "^4.0.3"
|
||||||
|
|
||||||
|
react-popper@^2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba"
|
||||||
|
integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==
|
||||||
|
dependencies:
|
||||||
|
react-fast-compare "^3.0.1"
|
||||||
|
warning "^4.0.2"
|
||||||
|
|
||||||
react-refresh@^0.14.0:
|
react-refresh@^0.14.0:
|
||||||
version "0.14.0"
|
version "0.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
|
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
|
||||||
@@ -9950,6 +9978,11 @@ synchronous-promise@^2.0.15:
|
|||||||
resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.17.tgz#38901319632f946c982152586f2caf8ddc25c032"
|
resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.17.tgz#38901319632f946c982152586f2caf8ddc25c032"
|
||||||
integrity sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==
|
integrity sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==
|
||||||
|
|
||||||
|
tabbable@^6.2.0:
|
||||||
|
version "6.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97"
|
||||||
|
integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==
|
||||||
|
|
||||||
taffydb@2.6.2:
|
taffydb@2.6.2:
|
||||||
version "2.6.2"
|
version "2.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268"
|
resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268"
|
||||||
@@ -10595,7 +10628,7 @@ walker@^1.0.8:
|
|||||||
dependencies:
|
dependencies:
|
||||||
makeerror "1.0.12"
|
makeerror "1.0.12"
|
||||||
|
|
||||||
warning@^4.0.3:
|
warning@^4.0.2, warning@^4.0.3:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
||||||
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
|
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
|
||||||
|
|||||||
Reference in New Issue
Block a user