🔍 Improvements to CV search results
This commit is contained in:
@@ -7,7 +7,7 @@ import Card from "../shared/Carda";
|
|||||||
import ellipsize from "ellipsize";
|
import ellipsize from "ellipsize";
|
||||||
import { convert } from "html-to-text";
|
import { convert } from "html-to-text";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
COMICVINE_SERVICE_URI,
|
COMICVINE_SERVICE_URI,
|
||||||
LIBRARY_SERVICE_BASE_URI,
|
LIBRARY_SERVICE_BASE_URI,
|
||||||
@@ -23,32 +23,31 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [comicVineMetadata, setComicVineMetadata] = useState({});
|
const [comicVineMetadata, setComicVineMetadata] = useState({});
|
||||||
const getCVSearchResults = (searchQuery) => {
|
|
||||||
setSearchQuery(searchQuery.search);
|
|
||||||
};
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
mutate,
|
||||||
data: comicVineSearchResults,
|
data: comicVineSearchResults,
|
||||||
isLoading,
|
isPending,
|
||||||
isSuccess,
|
isSuccess,
|
||||||
} = useQuery({
|
} = useMutation({
|
||||||
queryFn: async () =>
|
mutationFn: async (data: { search: string; resource: string }) => {
|
||||||
await axios({
|
const { search, resource } = data;
|
||||||
|
console.log(data);
|
||||||
|
return await axios({
|
||||||
url: `${COMICVINE_SERVICE_URI}/search`,
|
url: `${COMICVINE_SERVICE_URI}/search`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
params: {
|
params: {
|
||||||
api_key: "a5fa0663683df8145a85d694b5da4b87e1c92c69",
|
api_key: "a5fa0663683df8145a85d694b5da4b87e1c92c69",
|
||||||
query: searchQuery,
|
query: search,
|
||||||
format: "json",
|
format: "json",
|
||||||
limit: "10",
|
limit: "10",
|
||||||
offset: "0",
|
offset: "0",
|
||||||
field_list:
|
field_list:
|
||||||
"id,name,deck,api_detail_url,image,description,volume,cover_date",
|
"id,name,deck,api_detail_url,image,description,volume,cover_date,count_of_issues",
|
||||||
resources: "issue",
|
resources: resource,
|
||||||
},
|
},
|
||||||
}),
|
});
|
||||||
queryKey: ["comicvineSearchResults", searchQuery],
|
},
|
||||||
enabled: !isNil(searchQuery),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// add to library
|
// add to library
|
||||||
@@ -107,7 +106,7 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
</header>
|
</header>
|
||||||
<div className="mx-auto max-w-screen-sm px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
|
<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={mutate}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
...formData,
|
...formData,
|
||||||
}}
|
}}
|
||||||
@@ -139,19 +138,62 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
Search
|
Search
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{/* resource type selection: volume, issue etc. */}
|
||||||
|
<div className="flex flex-row gap-3 mt-4">
|
||||||
|
<Field name="resource" type="radio" value="volume">
|
||||||
|
{({ input: volumesInput, meta }) => (
|
||||||
|
<div className="w-fit rounded-xl">
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
{...volumesInput}
|
||||||
|
type="radio"
|
||||||
|
id="volume"
|
||||||
|
className="peer hidden"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="volume"
|
||||||
|
className="block cursor-pointer select-none rounded-xl p-2 text-center peer-checked:bg-blue-500 peer-checked:font-bold peer-checked:text-white"
|
||||||
|
>
|
||||||
|
Volumes
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field name="resource" type="radio" value="issue">
|
||||||
|
{({ input: issuesInput, meta }) => (
|
||||||
|
<div className="w-fit rounded-xl">
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
{...issuesInput}
|
||||||
|
type="radio"
|
||||||
|
id="issue"
|
||||||
|
className="peer hidden"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="issue"
|
||||||
|
className="block cursor-pointer select-none rounded-xl p-2 text-center peer-checked:bg-blue-500 peer-checked:font-bold peer-checked:text-white"
|
||||||
|
>
|
||||||
|
Issues
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isLoading && <>Loading kaka...</>}
|
{isPending && <>Loading results...</>}
|
||||||
{!isNil(comicVineSearchResults?.data.results) &&
|
{!isEmpty(comicVineSearchResults?.data?.results) ? (
|
||||||
!isEmpty(comicVineSearchResults?.data.results) ? (
|
|
||||||
<div className="mx-auto max-w-screen-xl px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
|
<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 result.resource_type === "issue" ? (
|
||||||
<div key={result.id} className="mb-5">
|
<div key={result.id} className="mb-5">
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<div className="mr-5">
|
<div className="mr-5 min-w-[200px] max-w-[25%]">
|
||||||
<Card
|
<Card
|
||||||
key={result.id}
|
key={result.id}
|
||||||
orientation={"cover-only"}
|
orientation={"cover-only"}
|
||||||
@@ -159,7 +201,7 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
hasDetails={false}
|
hasDetails={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="column">
|
<div className="w-3/4">
|
||||||
<div className="text-xl">
|
<div className="text-xl">
|
||||||
{!isEmpty(result.volume.name) ? (
|
{!isEmpty(result.volume.name) ? (
|
||||||
result.volume.name
|
result.volume.name
|
||||||
@@ -167,22 +209,14 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
<span className="is-size-3">No Name</span>
|
<span className="is-size-3">No Name</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="field is-grouped mt-1">
|
{result.cover_date && (
|
||||||
<div className="control">
|
<p>
|
||||||
<div className="tags has-addons">
|
<span className="tag is-light">Cover date</span>
|
||||||
<span className="tag is-light">Cover date</span>
|
{dayjs(result.cover_date).format("MMM D, YYYY")}
|
||||||
<span className="tag is-info is-light">
|
</p>
|
||||||
{dayjs(result.cover_date).format("MMM D, YYYY")}
|
)}
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="control">
|
<p className="tag is-warning">{result.id}</p>
|
||||||
<div className="tags has-addons">
|
|
||||||
<span className="tag is-warning">{result.id}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a href={result.api_detail_url}>
|
<a href={result.api_detail_url}>
|
||||||
{result.api_detail_url}
|
{result.api_detail_url}
|
||||||
@@ -210,7 +244,72 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>Loading</div>
|
result.resource_type === "volume" && (
|
||||||
|
<div key={result.id} className="mb-5">
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<div className="mr-5">
|
||||||
|
<Card
|
||||||
|
key={result.id}
|
||||||
|
orientation={"cover-only"}
|
||||||
|
imageUrl={result.image.small_url}
|
||||||
|
hasDetails={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="column">
|
||||||
|
<div className="text-xl">
|
||||||
|
{!isEmpty(result.name) ? (
|
||||||
|
result.name
|
||||||
|
) : (
|
||||||
|
<span className="is-size-3">No Name</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="field is-grouped mt-1">
|
||||||
|
<div className="control">
|
||||||
|
<div className="tags has-addons">
|
||||||
|
<span className="tag is-light">
|
||||||
|
Number of issues
|
||||||
|
</span>
|
||||||
|
<span className="tag is-info is-light">
|
||||||
|
{result.count_of_issues}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="control">
|
||||||
|
<div className="tags has-addons">
|
||||||
|
<span className="tag is-warning">
|
||||||
|
{result.id}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href={result.api_detail_url}>
|
||||||
|
{result.api_detail_url}
|
||||||
|
</a>
|
||||||
|
<p>
|
||||||
|
{ellipsize(
|
||||||
|
convert(result.description, {
|
||||||
|
baseElements: {
|
||||||
|
selectors: ["p", "div"],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
320,
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<div className="mt-2">
|
||||||
|
<button
|
||||||
|
className="flex space-x-1 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-2 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
||||||
|
onClick={() => addToLibrary("comicvine", result)}
|
||||||
|
>
|
||||||
|
<i className="icon-[solar--add-square-bold-duotone] w-6 h-6 mr-2"></i>{" "}
|
||||||
|
Mark as Wanted
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,13 +48,11 @@ export const SystemSettingsForm = (): ReactElement => {
|
|||||||
</article>
|
</article>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className={
|
className="flex space-x-1 sm:mt-0 sm:flex-row sm:items-center rounded-lg border border-red-400 dark:border-red-200 bg-red-200 px-4 py-2 text-gray-500 hover:bg-transparent hover:text-red-600 focus:outline-none focus:ring active:text-indigo-500"
|
||||||
isLoading ? "button is-danger is-loading" : "button is-danger"
|
|
||||||
}
|
|
||||||
onClick={() => flushDb()}
|
onClick={() => flushDb()}
|
||||||
>
|
>
|
||||||
<span className="icon">
|
<span className="pt-1 px-1">
|
||||||
<i className="fas fa-eraser"></i>
|
<i className="icon-[solar--trash-bin-trash-bold-duotone] w-7 h-7"></i>
|
||||||
</span>
|
</span>
|
||||||
<span>Flush DB & Temporary Folders</span>
|
<span>Flush DB & Temporary Folders</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const VolumeDetails = (props): ReactElement => {
|
|||||||
}),
|
}),
|
||||||
queryKey: ["comicObject"],
|
queryKey: ["comicObject"],
|
||||||
});
|
});
|
||||||
console.log(comicObject);
|
|
||||||
// get issues for a series
|
// get issues for a series
|
||||||
const {
|
const {
|
||||||
data: issuesForSeries,
|
data: issuesForSeries,
|
||||||
@@ -108,7 +108,6 @@ const VolumeDetails = (props): ReactElement => {
|
|||||||
status,
|
status,
|
||||||
} = useGetStoryArcs();
|
} = useGetStoryArcs();
|
||||||
|
|
||||||
console.log("jihya", issuesForSeries);
|
|
||||||
const IssuesInVolume = () => (
|
const IssuesInVolume = () => (
|
||||||
<>
|
<>
|
||||||
{!isUndefined(issuesForSeries) ? (
|
{!isUndefined(issuesForSeries) ? (
|
||||||
@@ -146,13 +145,42 @@ const VolumeDetails = (props): ReactElement => {
|
|||||||
|
|
||||||
const Issues = () => (
|
const Issues = () => (
|
||||||
<>
|
<>
|
||||||
as
|
<article
|
||||||
<ul>
|
role="alert"
|
||||||
|
className="mt-4 rounded-lg text-sm 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"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
You can add a single issue or the whole volume, and it will be added
|
||||||
|
to the list of `Wanted` items.
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<div className="flex flex-wrap">
|
||||||
{isSuccess &&
|
{isSuccess &&
|
||||||
issuesForSeries?.data.map((issue) => {
|
issuesForSeries?.data.map((issue) => {
|
||||||
return <li>{JSON.stringify(issue, null, 2)}</li>;
|
return (
|
||||||
|
<div className="my-3 dark:bg-slate-400 bg-slate-300 p-4 rounded-lg w-3/4">
|
||||||
|
<div className="flex flex-row gap-4 mb-2">
|
||||||
|
<div className="w-fit">
|
||||||
|
<img
|
||||||
|
src={issue.image.thumb_url}
|
||||||
|
className="w-full rounded-md"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-3/4">
|
||||||
|
<p className="text-xl">{issue.name}</p>
|
||||||
|
<p className="text-sm">
|
||||||
|
{convert(issue.description, {
|
||||||
|
baseElements: {
|
||||||
|
selectors: ["p"],
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -265,9 +293,9 @@ const VolumeDetails = (props): ReactElement => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className="field is-grouped mt-2">
|
<div className="field is-grouped">
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<span className="text-xl">
|
<span className="text-2xl">
|
||||||
{sourcedMetadata.comicvine.volumeInformation.name}
|
{sourcedMetadata.comicvine.volumeInformation.name}
|
||||||
</span>
|
</span>
|
||||||
{/* Comicvine Id */}
|
{/* Comicvine Id */}
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ export const Volumes = (props): ReactElement => {
|
|||||||
hasDetails={false}
|
hasDetails={false}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="dark:bg-[#647587] bg-slate-200 p-3 rounded-lg h-fit">
|
<div className="dark:bg-[#647587] bg-slate-200 rounded-lg w-3/4 h-fit p-3">
|
||||||
<span className="text-xl mb-1">
|
<div className="text-xl mb-1 w-fit">
|
||||||
{sourcedMetadata.comicvine.volumeInformation.name}
|
{sourcedMetadata.comicvine.volumeInformation.name}
|
||||||
</span>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
{ellipsize(
|
{ellipsize(
|
||||||
convert(
|
convert(
|
||||||
@@ -72,7 +72,7 @@ export const Volumes = (props): ReactElement => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
120,
|
180,
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user