From e59fd98b43070898c594c35af1a0bbee604ad160 Mon Sep 17 00:00:00 2001 From: Mohamed Bassem Date: Wed, 4 Feb 2026 14:02:05 +0000 Subject: feat(import): new import details page (#2451) * feat(import): new import details page * fix typecheck * review comments --- apps/web/app/settings/import/[sessionId]/page.tsx | 20 + apps/web/components/settings/ImportSessionCard.tsx | 6 + .../components/settings/ImportSessionDetail.tsx | 596 +++++++++++++++++++++ apps/web/lib/hooks/useImportSessions.ts | 20 +- apps/web/lib/i18n/locales/en/translation.json | 26 +- 5 files changed, 666 insertions(+), 2 deletions(-) create mode 100644 apps/web/app/settings/import/[sessionId]/page.tsx create mode 100644 apps/web/components/settings/ImportSessionDetail.tsx diff --git a/apps/web/app/settings/import/[sessionId]/page.tsx b/apps/web/app/settings/import/[sessionId]/page.tsx new file mode 100644 index 00000000..968de13a --- /dev/null +++ b/apps/web/app/settings/import/[sessionId]/page.tsx @@ -0,0 +1,20 @@ +import type { Metadata } from "next"; +import ImportSessionDetail from "@/components/settings/ImportSessionDetail"; +import { useTranslation } from "@/lib/i18n/server"; + +export async function generateMetadata(): Promise { + // oxlint-disable-next-line rules-of-hooks + const { t } = await useTranslation(); + return { + title: `${t("settings.import_sessions.detail.page_title")} | Karakeep`, + }; +} + +export default async function ImportSessionDetailPage({ + params, +}: { + params: Promise<{ sessionId: string }>; +}) { + const { sessionId } = await params; + return ; +} diff --git a/apps/web/components/settings/ImportSessionCard.tsx b/apps/web/components/settings/ImportSessionCard.tsx index f20710ca..f62a00dd 100644 --- a/apps/web/components/settings/ImportSessionCard.tsx +++ b/apps/web/components/settings/ImportSessionCard.tsx @@ -242,6 +242,12 @@ export function ImportSessionCard({ session }: ImportSessionCardProps) { {/* Actions */}
+ {canPause && ( + )} + {canResume && ( + + )} + {canDelete && ( + + {t("settings.import_sessions.delete_dialog_description", { + name: stats.name, + })} +
+ } + actionButton={(setDialogOpen) => ( + + )} + > + + + )} +
+ + + + + {/* Filter tabs + Results table */} +
+ setFilter(v as FilterType)} + className="w-full" + > + + + {t("settings.import_sessions.detail.filter_all")} + + + {t("settings.import_sessions.detail.filter_accepted")} + + + {t("settings.import_sessions.detail.filter_rejected")} + + + {t("settings.import_sessions.detail.filter_duplicates")} + + + {t("settings.import_sessions.detail.filter_pending")} + + + + + {isResultsLoading ? ( + + ) : items.length === 0 ? ( +

+ {t("settings.import_sessions.detail.no_results")} +

+ ) : ( +
+ + + + + {t("settings.import_sessions.detail.table_title")} + + + {t("settings.import_sessions.detail.table_type")} + + + {t("settings.import_sessions.detail.table_result")} + + + {t("settings.import_sessions.detail.table_reason")} + + + {t("settings.import_sessions.detail.table_bookmark")} + + + + + {items.map((item) => ( + + + {getTitleDisplay( + item, + t("settings.import_sessions.detail.no_title"), + )} + + + + {getTypeIcon(item.type)} + {getTypeLabel(item.type, t)} + + + + {getResultBadge(item.status, item.result, t)} + + + {item.resultReason || "—"} + + + {item.resultBookmarkId ? ( + + + {t("settings.import_sessions.detail.view_bookmark")} + + ) : ( + + )} + + + ))} + +
+ {hasNextPage && ( +
+ fetchNextPage()} + variant="ghost" + > + {t("settings.import_sessions.detail.load_more")} + +
+ )} +
+ )} +
+ + ); +} diff --git a/apps/web/lib/hooks/useImportSessions.ts b/apps/web/lib/hooks/useImportSessions.ts index 4d095c0b..2cc632ad 100644 --- a/apps/web/lib/hooks/useImportSessions.ts +++ b/apps/web/lib/hooks/useImportSessions.ts @@ -1,7 +1,12 @@ "use client"; import { toast } from "@/components/ui/sonner"; -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { + useInfiniteQuery, + useMutation, + useQuery, + useQueryClient, +} from "@tanstack/react-query"; import { useTRPC } from "@karakeep/shared-react/trpc"; @@ -131,3 +136,16 @@ export function useResumeImportSession() { }), ); } + +export function useImportSessionResults( + importSessionId: string, + filter: "all" | "accepted" | "rejected" | "skipped_duplicate" | "pending", +) { + const api = useTRPC(); + return useInfiniteQuery( + api.importSessions.getImportSessionResults.infiniteQueryOptions( + { importSessionId, filter, limit: 50 }, + { getNextPageParam: (lastPage) => lastPage.nextCursor }, + ), + ); +} diff --git a/apps/web/lib/i18n/locales/en/translation.json b/apps/web/lib/i18n/locales/en/translation.json index 59d2098e..06cfd7a1 100644 --- a/apps/web/lib/i18n/locales/en/translation.json +++ b/apps/web/lib/i18n/locales/en/translation.json @@ -435,7 +435,31 @@ "delete_dialog_description": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone. The bookmarks themselves will not be deleted.", "delete_session": "Delete Session", "pause_session": "Pause", - "resume_session": "Resume" + "resume_session": "Resume", + "view_details": "View Details", + "detail": { + "page_title": "Import Session Details", + "back_to_import": "Back to Import", + "filter_all": "All", + "filter_accepted": "Accepted", + "filter_rejected": "Rejected", + "filter_duplicates": "Duplicates", + "filter_pending": "Pending", + "table_title": "Title / URL", + "table_type": "Type", + "table_result": "Result", + "table_reason": "Reason", + "table_bookmark": "Bookmark", + "result_accepted": "Accepted", + "result_rejected": "Rejected", + "result_skipped_duplicate": "Duplicate", + "result_pending": "Pending", + "result_processing": "Processing", + "no_results": "No results found for this filter.", + "view_bookmark": "View Bookmark", + "load_more": "Load More", + "no_title": "No title" + } }, "backups": { "backups": "Backups", -- cgit v1.2.3-70-g09d2