📝 State issue fixes
This commit is contained in:
@@ -9,9 +9,9 @@ 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";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import useEmblaCarousel from "embla-carousel-react";
|
import useEmblaCarousel from "embla-carousel-react";
|
||||||
import { COMICVINE_SERVICE_URI } from "../../constants/endpoints";
|
import { COMICVINE_SERVICE_URI, LIBRARY_SERVICE_BASE_URI } from "../../constants/endpoints";
|
||||||
import { Field, Form } from "react-final-form";
|
import { Field, Form } from "react-final-form";
|
||||||
import DatePickerDialog from "../shared/DatePicker";
|
import DatePickerDialog from "../shared/DatePicker";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
@@ -27,6 +27,8 @@ const http = rateLimiter(axios.create(), {
|
|||||||
});
|
});
|
||||||
const cachedAxios = setupCache(axios);
|
const cachedAxios = setupCache(axios);
|
||||||
export const PullList = (): ReactElement => {
|
export const PullList = (): ReactElement => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
// datepicker
|
// datepicker
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
const [inputValue, setInputValue] = useState<string>(
|
const [inputValue, setInputValue] = useState<string>(
|
||||||
@@ -55,8 +57,38 @@ export const PullList = (): ReactElement => {
|
|||||||
}),
|
}),
|
||||||
queryKey: ["pullList", inputValue],
|
queryKey: ["pullList", inputValue],
|
||||||
});
|
});
|
||||||
const addToLibrary = (sourceName: string, locgMetadata) =>
|
|
||||||
importToDB(sourceName, { locg: locgMetadata });
|
const { mutate: addToLibrary } = useMutation({
|
||||||
|
mutationFn: async ({ sourceName, metadata }: { sourceName: string; metadata: any }) => {
|
||||||
|
const comicBookMetadata = {
|
||||||
|
importType: "new",
|
||||||
|
payload: {
|
||||||
|
rawFileDetails: {
|
||||||
|
name: "",
|
||||||
|
},
|
||||||
|
importStatus: {
|
||||||
|
isImported: true,
|
||||||
|
tagged: false,
|
||||||
|
matchedResult: {
|
||||||
|
score: "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sourcedMetadata: metadata || null,
|
||||||
|
acquisition: { source: { wanted: true, name: sourceName } },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return await axios.request({
|
||||||
|
url: `${LIBRARY_SERVICE_BASE_URI}/rawImportToDb`,
|
||||||
|
method: "POST",
|
||||||
|
data: comicBookMetadata,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
// Invalidate and refetch wanted comics queries
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["wantedComics"] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const next = () => {
|
const next = () => {
|
||||||
// sliderRef.slickNext();
|
// sliderRef.slickNext();
|
||||||
@@ -135,7 +167,7 @@ export const PullList = (): ReactElement => {
|
|||||||
<div className="flex flex-row justify-end">
|
<div className="flex flex-row justify-end">
|
||||||
<button
|
<button
|
||||||
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"
|
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({ sourceName: "locg", metadata: { locg: issue } })}
|
||||||
>
|
>
|
||||||
<i className="icon-[solar--add-square-bold-duotone] w-5 h-5 mr-2"></i>{" "}
|
<i className="icon-[solar--add-square-bold-duotone] w-5 h-5 mr-2"></i>{" "}
|
||||||
Want
|
Want
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ interface IProps {
|
|||||||
|
|
||||||
export const Import = (props: IProps): ReactElement => {
|
export const Import = (props: IProps): ReactElement => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { importJobQueue, socketIOInstance } = useStore(
|
const { importJobQueue, getSocket } = useStore(
|
||||||
useShallow((state) => ({
|
useShallow((state) => ({
|
||||||
importJobQueue: state.importJobQueue,
|
importJobQueue: state.importJobQueue,
|
||||||
socketIOInstance: state.socketIOInstance,
|
getSocket: state.getSocket,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -44,24 +44,57 @@ export const Import = (props: IProps): ReactElement => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, isError, isLoading } = useQuery({
|
const { data, isError, isLoading, refetch } = useQuery({
|
||||||
queryKey: ["allImportJobResults"],
|
queryKey: ["allImportJobResults"],
|
||||||
queryFn: async () =>
|
queryFn: async () => {
|
||||||
await axios({
|
const response = await axios({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "http://localhost:3000/api/jobqueue/getJobResultStatistics",
|
url: "http://localhost:3000/api/jobqueue/getJobResultStatistics",
|
||||||
}),
|
params: { _t: Date.now() }, // Cache busting
|
||||||
|
});
|
||||||
|
console.log("Fetched import results:", response.data);
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
staleTime: 0, // Always consider data stale
|
||||||
|
gcTime: 0, // Don't cache the data (formerly cacheTime)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Ensure socket connection is established and listen for import completion
|
||||||
|
useEffect(() => {
|
||||||
|
const socket = getSocket("/");
|
||||||
|
|
||||||
|
// Listen for import queue drained event to refresh the table
|
||||||
|
const handleQueueDrained = () => {
|
||||||
|
console.log("Import queue drained, refreshing table...");
|
||||||
|
refetch();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Listen for individual import completions to refresh the table
|
||||||
|
const handleCoverExtracted = () => {
|
||||||
|
console.log("Cover extracted, refreshing table...");
|
||||||
|
refetch();
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.on("LS_IMPORT_QUEUE_DRAINED", handleQueueDrained);
|
||||||
|
socket.on("LS_COVER_EXTRACTED", handleCoverExtracted);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.off("LS_IMPORT_QUEUE_DRAINED", handleQueueDrained);
|
||||||
|
socket.off("LS_COVER_EXTRACTED", handleCoverExtracted);
|
||||||
|
};
|
||||||
|
}, [getSocket, refetch]);
|
||||||
|
|
||||||
const toggleQueue = (queueAction: string, queueStatus: string) => {
|
const toggleQueue = (queueAction: string, queueStatus: string) => {
|
||||||
socketIOInstance.emit(
|
const socket = getSocket("/");
|
||||||
|
socket.emit(
|
||||||
"call",
|
"call",
|
||||||
"socket.setQueueStatus",
|
"socket.setQueueStatus",
|
||||||
{
|
{
|
||||||
queueAction,
|
queueAction,
|
||||||
queueStatus,
|
queueStatus,
|
||||||
},
|
},
|
||||||
(data) => console.log(data),
|
(data: any) => console.log(data),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
@@ -155,21 +188,21 @@ export const Import = (props: IProps): ReactElement => {
|
|||||||
</article>
|
</article>
|
||||||
|
|
||||||
<div className="my-4">
|
<div className="my-4">
|
||||||
{importJobQueue.status === "drained" ||
|
{(importJobQueue.status === "drained" ||
|
||||||
(importJobQueue.status === undefined && (
|
importJobQueue.status === undefined) && (
|
||||||
<button
|
<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-5 py-3 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
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-5 py-3 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
initiateImport();
|
initiateImport();
|
||||||
importJobQueue.setStatus("running");
|
importJobQueue.setStatus("running");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="text-md">Start Import</span>
|
<span className="text-md">Start Import</span>
|
||||||
<span className="w-6 h-6">
|
<span className="w-6 h-6">
|
||||||
<i className="h-6 w-6 icon-[solar--file-left-bold-duotone]"></i>
|
<i className="h-6 w-6 icon-[solar--file-left-bold-duotone]"></i>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Activity */}
|
{/* Activity */}
|
||||||
@@ -230,6 +263,9 @@ export const Import = (props: IProps): ReactElement => {
|
|||||||
<table className="min-w-full divide-y-2 divide-gray-200 dark:divide-gray-200 text-md">
|
<table className="min-w-full divide-y-2 divide-gray-200 dark:divide-gray-200 text-md">
|
||||||
<thead className="ltr:text-left rtl:text-right">
|
<thead className="ltr:text-left rtl:text-right">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th className="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-slate-200">
|
||||||
|
#
|
||||||
|
</th>
|
||||||
<th className="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-slate-200">
|
<th className="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-slate-200">
|
||||||
Time Started
|
Time Started
|
||||||
</th>
|
</th>
|
||||||
@@ -246,9 +282,12 @@ export const Import = (props: IProps): ReactElement => {
|
|||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody className="divide-y divide-gray-200">
|
<tbody className="divide-y divide-gray-200">
|
||||||
{data?.data.map((jobResult, id) => {
|
{data?.data.map((jobResult: any, index: number) => {
|
||||||
return (
|
return (
|
||||||
<tr key={id}>
|
<tr key={index}>
|
||||||
|
<td className="whitespace-nowrap px-4 py-2 text-gray-700 dark:text-slate-300 font-medium">
|
||||||
|
{index + 1}
|
||||||
|
</td>
|
||||||
<td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300">
|
<td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300">
|
||||||
{format(
|
{format(
|
||||||
new Date(jobResult.earliestTimestamp),
|
new Date(jobResult.earliestTimestamp),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import "../../shared/utils/i18n.util";
|
import "../../shared/utils/i18n.util";
|
||||||
import PopoverButton from "../shared/PopoverButton";
|
import PopoverButton from "../shared/PopoverButton";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
COMICVINE_SERVICE_URI,
|
COMICVINE_SERVICE_URI,
|
||||||
LIBRARY_SERVICE_BASE_URI,
|
LIBRARY_SERVICE_BASE_URI,
|
||||||
@@ -20,6 +20,7 @@ import axios from "axios";
|
|||||||
interface ISearchProps {}
|
interface ISearchProps {}
|
||||||
|
|
||||||
export const Search = ({}: ISearchProps): ReactElement => {
|
export const Search = ({}: ISearchProps): ReactElement => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const formData = {
|
const formData = {
|
||||||
search: "",
|
search: "",
|
||||||
};
|
};
|
||||||
@@ -138,6 +139,10 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
// Invalidate and refetch wanted comics queries
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["wantedComics"] });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const addToLibrary = (sourceName: string, comicData) =>
|
const addToLibrary = (sourceName: string, comicData) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user