diff --git a/src/lib/client/stores/dataPage.ts b/src/lib/client/stores/dataPage.ts
new file mode 100644
index 0000000..b10db78
--- /dev/null
+++ b/src/lib/client/stores/dataPage.ts
@@ -0,0 +1,98 @@
+/**
+ * Data page store for managing search, view toggle, and filtering
+ * Combines search functionality with view state persistence
+ */
+
+import { writable, derived } from 'svelte/store';
+import { browser } from '$app/environment';
+import { createSearchStore, type SearchStore } from './search';
+
+export type ViewMode = 'table' | 'cards';
+
+export interface DataPageConfig {
+ /** Key for localStorage persistence */
+ storageKey: string;
+ /** Fields to search within items */
+ searchKeys: (keyof T)[];
+ /** Default view mode */
+ defaultView?: ViewMode;
+ /** Debounce time for search in ms */
+ debounceMs?: number;
+}
+
+export interface DataPageStore {
+ /** Search store for SearchAction component */
+ search: SearchStore;
+ /** Current view mode ('table' | 'cards') */
+ view: {
+ subscribe: (fn: (value: ViewMode) => void) => () => void;
+ set: (value: ViewMode) => void;
+ };
+ /** Filtered items based on search query */
+ filtered: {
+ subscribe: (fn: (value: T[]) => void) => () => void;
+ };
+ /** Update the items (e.g., when data changes) */
+ setItems: (items: T[]) => void;
+}
+
+/**
+ * Create a data page store for managing list pages
+ *
+ * @example
+ * const { search, view, filtered } = createDataPageStore(data.profiles, {
+ * storageKey: 'qualityProfilesView',
+ * searchKeys: ['name', 'description']
+ * });
+ */
+export function createDataPageStore(
+ initialItems: T[],
+ config: DataPageConfig
+): DataPageStore {
+ const { storageKey, searchKeys, defaultView = 'table', debounceMs = 300 } = config;
+
+ // Items store
+ const items = writable(initialItems);
+
+ // Search store
+ const search = createSearchStore({ debounceMs });
+
+ // View store with localStorage persistence
+ const storedView = browser ? (localStorage.getItem(storageKey) as ViewMode | null) : null;
+ const view = writable(storedView ?? defaultView);
+
+ // Persist view changes to localStorage
+ if (browser) {
+ view.subscribe((value) => {
+ localStorage.setItem(storageKey, value);
+ });
+ }
+
+ // Filtered items derived from search
+ const filtered = derived([items, search.debouncedQuery], ([$items, $query]) => {
+ if (!$query) return $items;
+
+ const queryLower = $query.toLowerCase();
+ return $items.filter((item) =>
+ searchKeys.some((key) => {
+ const value = item[key];
+ if (value == null) return false;
+ return String(value).toLowerCase().includes(queryLower);
+ })
+ );
+ });
+
+ return {
+ search,
+ view: {
+ subscribe: view.subscribe,
+ set: view.set
+ },
+ filtered: {
+ subscribe: filtered.subscribe
+ },
+ setItems: (newItems: T[]) => items.set(newItems)
+ };
+}
+
+export type { SearchStore };
diff --git a/src/lib/client/ui/actions/ViewToggle.svelte b/src/lib/client/ui/actions/ViewToggle.svelte
new file mode 100644
index 0000000..55a66ca
--- /dev/null
+++ b/src/lib/client/ui/actions/ViewToggle.svelte
@@ -0,0 +1,27 @@
+
+
+
+
+ (value = 'cards')}
+ />
+ (value = 'table')}
+ />
+
+
diff --git a/src/routes/quality-profiles/[databaseId]/+page.svelte b/src/routes/quality-profiles/[databaseId]/+page.svelte
index edb48ef..8063367 100644
--- a/src/routes/quality-profiles/[databaseId]/+page.svelte
+++ b/src/routes/quality-profiles/[databaseId]/+page.svelte
@@ -1,36 +1,23 @@
@@ -59,23 +37,8 @@
-
-
-
- (currentView = 'cards')}
- />
- (currentView = 'table')}
- />
-
-
+
+
@@ -88,7 +51,7 @@
No quality profiles found for {data.currentDatabase.name}
- {:else if filteredProfiles.length === 0}
+ {:else if $filtered.length === 0}
@@ -96,10 +59,10 @@
No quality profiles match your search
- {:else if currentView === 'table'}
-
+ {:else if $view === 'table'}
+
{:else}
-
+
{/if}