fix(app): show empty state in import sheet when no sessions match
This commit is contained in:
@@ -61,6 +61,17 @@ vi.mock("@/components/provider-icons", () => ({
|
|||||||
getProviderIcon: () => () => null,
|
getProviderIcon: () => () => null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("lucide-react-native", () => {
|
||||||
|
const icon = (name: string) => {
|
||||||
|
const Icon = () => React.createElement("span", { "data-icon": name });
|
||||||
|
Icon.displayName = name;
|
||||||
|
return Icon;
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
Inbox: icon("Inbox"),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock("@/components/ui/loading-spinner", () => ({
|
vi.mock("@/components/ui/loading-spinner", () => ({
|
||||||
LoadingSpinner: () =>
|
LoadingSpinner: () =>
|
||||||
React.createElement("span", { "data-testid": "workspace-import-loading-spinner" }),
|
React.createElement("span", { "data-testid": "workspace-import-loading-spinner" }),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useMutation, useQueries, useQueryClient } from "@tanstack/react-query";
|
|||||||
import type { DaemonClient, FetchRecentProviderSessionEntry } from "@server/client/daemon-client";
|
import type { DaemonClient, FetchRecentProviderSessionEntry } from "@server/client/daemon-client";
|
||||||
import type { AgentProvider } from "@server/server/agent/agent-sdk-types";
|
import type { AgentProvider } from "@server/server/agent/agent-sdk-types";
|
||||||
import { IMPORTABLE_PROVIDERS } from "@server/shared/importable-providers";
|
import { IMPORTABLE_PROVIDERS } from "@server/shared/importable-providers";
|
||||||
|
import { Inbox } from "lucide-react-native";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { AdaptiveModalSheet, type SheetHeader } from "@/components/adaptive-modal-sheet";
|
import { AdaptiveModalSheet, type SheetHeader } from "@/components/adaptive-modal-sheet";
|
||||||
import { LoadingSpinner } from "@/components/ui/loading-spinner";
|
import { LoadingSpinner } from "@/components/ui/loading-spinner";
|
||||||
@@ -172,8 +173,6 @@ interface SheetStatusMessagesProps {
|
|||||||
allQueriesErrored: boolean;
|
allQueriesErrored: boolean;
|
||||||
erroredProviderLabels: ReadonlyArray<string>;
|
erroredProviderLabels: ReadonlyArray<string>;
|
||||||
importErrored: boolean;
|
importErrored: boolean;
|
||||||
showEmptyState: boolean;
|
|
||||||
allAlreadyImported: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetStatusMessages({
|
function SheetStatusMessages({
|
||||||
@@ -184,8 +183,6 @@ function SheetStatusMessages({
|
|||||||
allQueriesErrored,
|
allQueriesErrored,
|
||||||
erroredProviderLabels,
|
erroredProviderLabels,
|
||||||
importErrored,
|
importErrored,
|
||||||
showEmptyState,
|
|
||||||
allAlreadyImported,
|
|
||||||
}: SheetStatusMessagesProps) {
|
}: SheetStatusMessagesProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
if (!isClientReady) {
|
if (!isClientReady) {
|
||||||
@@ -216,17 +213,58 @@ function SheetStatusMessages({
|
|||||||
{importErrored ? (
|
{importErrored ? (
|
||||||
<Text style={styles.statusText}>Could not import selected session.</Text>
|
<Text style={styles.statusText}>Could not import selected session.</Text>
|
||||||
) : null}
|
) : null}
|
||||||
{showEmptyState ? (
|
|
||||||
<Text style={styles.statusText}>
|
|
||||||
{allAlreadyImported
|
|
||||||
? "All recent sessions are already imported."
|
|
||||||
: "No recent sessions to import."}
|
|
||||||
</Text>
|
|
||||||
) : null}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EmptyStateInputs {
|
||||||
|
isLoadingSessions: boolean;
|
||||||
|
allQueriesErrored: boolean;
|
||||||
|
isQueryingProviders: boolean;
|
||||||
|
allQueriesSettled: boolean;
|
||||||
|
selectedProvider: string;
|
||||||
|
aggregatedCount: number;
|
||||||
|
visibleCount: number;
|
||||||
|
totalAlreadyImportedCount: number;
|
||||||
|
providerLabelById: ReadonlyMap<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeEmptyState(input: EmptyStateInputs): {
|
||||||
|
showEmptyState: boolean;
|
||||||
|
emptyStateTitle: string;
|
||||||
|
} {
|
||||||
|
const showEmptyState =
|
||||||
|
!input.isLoadingSessions &&
|
||||||
|
!input.allQueriesErrored &&
|
||||||
|
input.isQueryingProviders &&
|
||||||
|
input.allQueriesSettled &&
|
||||||
|
input.visibleCount === 0;
|
||||||
|
if (!showEmptyState) {
|
||||||
|
return { showEmptyState, emptyStateTitle: "" };
|
||||||
|
}
|
||||||
|
const isFilteredEmpty = input.selectedProvider !== ALL_FILTER_VALUE && input.aggregatedCount > 0;
|
||||||
|
if (isFilteredEmpty) {
|
||||||
|
const label = input.providerLabelById.get(input.selectedProvider) ?? input.selectedProvider;
|
||||||
|
return { showEmptyState, emptyStateTitle: `No ${label} sessions found.` };
|
||||||
|
}
|
||||||
|
if (input.totalAlreadyImportedCount > 0) {
|
||||||
|
return { showEmptyState, emptyStateTitle: "All recent sessions are already imported." };
|
||||||
|
}
|
||||||
|
return { showEmptyState, emptyStateTitle: "No recent sessions to import." };
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetEmptyState({ title }: { title: string }) {
|
||||||
|
const { theme } = useUnistyles();
|
||||||
|
return (
|
||||||
|
<View style={styles.emptyState} testID="workspace-import-empty-state">
|
||||||
|
<View style={styles.emptyStateIcon}>
|
||||||
|
<Inbox size={theme.iconSize.lg} color={theme.colors.foregroundMuted} strokeWidth={1.5} />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.emptyStateTitle}>{title}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function buildProviderFilterOptions(
|
function buildProviderFilterOptions(
|
||||||
providers: ReadonlyArray<string>,
|
providers: ReadonlyArray<string>,
|
||||||
providerLabelById: ReadonlyMap<string, string>,
|
providerLabelById: ReadonlyMap<string, string>,
|
||||||
@@ -423,13 +461,17 @@ export function WorkspaceImportSheet({
|
|||||||
const allQueriesErrored = isQueryingProviders && queries.every((query) => query.isError);
|
const allQueriesErrored = isQueryingProviders && queries.every((query) => query.isError);
|
||||||
const allQueriesSettled =
|
const allQueriesSettled =
|
||||||
isQueryingProviders && queries.every((query) => !query.isLoading && !query.isPending);
|
isQueryingProviders && queries.every((query) => !query.isLoading && !query.isPending);
|
||||||
const showEmptyState =
|
const { showEmptyState, emptyStateTitle } = computeEmptyState({
|
||||||
!isLoadingSessions &&
|
isLoadingSessions,
|
||||||
!allQueriesErrored &&
|
allQueriesErrored,
|
||||||
isQueryingProviders &&
|
isQueryingProviders,
|
||||||
allQueriesSettled &&
|
allQueriesSettled,
|
||||||
aggregatedEntries.length === 0;
|
selectedProvider,
|
||||||
const allAlreadyImported = showEmptyState && totalAlreadyImportedCount > 0;
|
aggregatedCount: aggregatedEntries.length,
|
||||||
|
visibleCount: visibleEntries.length,
|
||||||
|
totalAlreadyImportedCount,
|
||||||
|
providerLabelById,
|
||||||
|
});
|
||||||
const showFilter = filterProviders.length > 1;
|
const showFilter = filterProviders.length > 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -464,8 +506,6 @@ export function WorkspaceImportSheet({
|
|||||||
allQueriesErrored={allQueriesErrored}
|
allQueriesErrored={allQueriesErrored}
|
||||||
erroredProviderLabels={erroredProviderLabels}
|
erroredProviderLabels={erroredProviderLabels}
|
||||||
importErrored={importMutation.isError}
|
importErrored={importMutation.isError}
|
||||||
showEmptyState={showEmptyState}
|
|
||||||
allAlreadyImported={allAlreadyImported}
|
|
||||||
/>
|
/>
|
||||||
{visibleEntries.length > 0 ? (
|
{visibleEntries.length > 0 ? (
|
||||||
<View style={styles.list}>
|
<View style={styles.list}>
|
||||||
@@ -480,6 +520,7 @@ export function WorkspaceImportSheet({
|
|||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
|
{showEmptyState ? <SheetEmptyState title={emptyStateTitle} /> : null}
|
||||||
</AdaptiveModalSheet>
|
</AdaptiveModalSheet>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -549,4 +590,20 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
},
|
},
|
||||||
|
emptyState: {
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
gap: theme.spacing[2],
|
||||||
|
paddingVertical: theme.spacing[8],
|
||||||
|
paddingHorizontal: theme.spacing[4],
|
||||||
|
},
|
||||||
|
emptyStateIcon: {
|
||||||
|
opacity: 0.6,
|
||||||
|
marginBottom: theme.spacing[1],
|
||||||
|
},
|
||||||
|
emptyStateTitle: {
|
||||||
|
color: theme.colors.foreground,
|
||||||
|
fontSize: theme.fontSize.base,
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user