learning_ai_notes/mobile/src/app/(tabs)/search.tsx
Saravana Kumar fd8afb2742
Some checks failed
CI — NoteLett / Web — typecheck + test + build (push) Failing after 2s
CI — NoteLett / Backend — typecheck + test + build (push) Failing after 2s
CI — NoteLett / Mobile — typecheck (push) Failing after 1s
CI — NoteLett / E2E — Playwright (push) Failing after 1s
fix: add react doctor and resolve reported errors
2026-05-28 00:35:28 +00:00

110 lines
3.4 KiB
TypeScript

import { useMemo, useState } from 'react';
import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
import { router } from 'expo-router';
import type { MobileNote } from '../../api/notes';
import type { MobileWorkspace } from '../../api/workspaces';
import { useNotesStore, type NotesState } from '../../store/notes-store';
import { useWorkspaceStore, type WorkspaceState } from '../../store/workspace-store';
import { buttonA11y, textInputA11y } from '../../lib/accessibility';
import { colors } from '../../theme';
export default function SearchScreen() {
const [query, setQuery] = useState('');
const notes = useNotesStore((state: NotesState) => state.notes);
const isLoading = useNotesStore((state: NotesState) => state.isLoading);
const workspaces = useWorkspaceStore((state: WorkspaceState) => state.workspaces);
const activeWorkspaceId = useWorkspaceStore((state: WorkspaceState) => state.activeWorkspaceId);
const activeWorkspaceName =
workspaces.find((workspace: MobileWorkspace) => workspace.id === activeWorkspaceId)?.name ?? null;
const results = useMemo(() => {
const scopedNotes = notes.filter((note: MobileNote) =>
activeWorkspaceName ? note.workspaceName === activeWorkspaceName : true
);
if (!query.trim()) {
return scopedNotes;
}
const normalized = query.toLowerCase();
return scopedNotes.filter(
(note: MobileNote) =>
note.title.toLowerCase().includes(normalized) ||
note.body.toLowerCase().includes(normalized) ||
note.workspaceName.toLowerCase().includes(normalized)
);
}, [activeWorkspaceName, notes, query]);
return (
<View style={styles.container}>
<Text style={styles.title}>Search</Text>
<Text style={styles.scope}>Scope: {activeWorkspaceName ?? 'All workspaces'}</Text>
<TextInput
{...textInputA11y('Search notes')}
value={query}
onChangeText={setQuery}
placeholder="Search notes"
placeholderTextColor={colors.textTertiary}
style={styles.input}
/>
<View style={styles.list}>
{isLoading ? <Text style={styles.empty}>Loading searchable notes</Text> : null}
{results.map((note: MobileNote) => (
<Pressable key={note.id} {...buttonA11y(`Open note ${note.title}`)} onPress={() => router.push(`/note/${note.id}`)} style={styles.row}>
<Text style={styles.rowText}>{note.title}</Text>
</Pressable>
))}
{!isLoading && results.length === 0 ? (
<Text style={styles.empty}>No notes match your search in this scope yet.</Text>
) : null}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: colors.bgCanvas,
gap: 14,
},
title: {
color: colors.textPrimary,
fontSize: 28,
fontWeight: '700',
},
input: {
borderWidth: 1,
borderColor: colors.borderDefault,
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 12,
color: colors.textPrimary,
backgroundColor: colors.surfaceCard,
},
scope: {
color: colors.textSecondary,
fontSize: 14,
},
list: {
gap: 10,
},
row: {
backgroundColor: colors.surfaceCard,
borderRadius: 14,
padding: 14,
borderWidth: 1,
borderColor: colors.borderDefault,
},
rowText: {
color: colors.textPrimary,
fontSize: 16,
fontWeight: '600',
},
empty: {
color: colors.textSecondary,
fontSize: 15,
},
});