📝 State issue fixes

This commit is contained in:
2026-02-24 15:48:38 -05:00
parent 37a2d0c75b
commit 0af9482be9
3 changed files with 107 additions and 31 deletions

View File

@@ -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

View File

@@ -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),

View File

@@ -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) =>