pottery-diary/src/screens/GlazePickerScreen.tsx

312 lines
8.7 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
FlatList,
TouchableOpacity,
TextInput,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useNavigation, useRoute, RouteProp } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { RootStackParamList } from '../navigation/types';
import { Glaze } from '../types';
import { getAllGlazes, searchGlazes } from '../lib/db/repositories';
import { Button, Card } from '../components';
import { colors, spacing, typography, borderRadius } from '../lib/theme';
import { useAuth } from '../contexts/AuthContext';
type NavigationProp = NativeStackNavigationProp<RootStackParamList, 'GlazePicker'>;
type RouteProps = RouteProp<RootStackParamList, 'GlazePicker'>;
export const GlazePickerScreen: React.FC = () => {
const navigation = useNavigation<NavigationProp>();
const route = useRoute<RouteProps>();
const { user } = useAuth();
const { selectedGlazeIds = [] } = route.params || {};
const [glazes, setGlazes] = useState<Glaze[]>([]);
const [selected, setSelected] = useState<string[]>(selectedGlazeIds);
const [searchQuery, setSearchQuery] = useState('');
const [loading, setLoading] = useState(true);
useEffect(() => {
loadGlazes();
}, []);
useEffect(() => {
if (searchQuery.trim()) {
searchGlazesHandler();
} else {
loadGlazes();
}
}, [searchQuery]);
const loadGlazes = async () => {
if (!user) return;
try {
const all = await getAllGlazes(user.id);
setGlazes(all);
} catch (error) {
console.error('Failed to load glazes:', error);
} finally {
setLoading(false);
}
};
const searchGlazesHandler = async () => {
if (!user) return;
try {
const results = await searchGlazes(searchQuery, user.id);
setGlazes(results);
} catch (error) {
console.error('Failed to search glazes:', error);
}
};
const toggleGlaze = (glazeId: string) => {
if (selected.includes(glazeId)) {
setSelected(selected.filter(id => id !== glazeId));
} else {
setSelected([...selected, glazeId]);
}
};
const handleSave = () => {
// Navigate back to the EXACT StepEditor instance we came from
// Using the same _editorKey ensures React Navigation finds the correct instance
console.log('GlazePicker: Navigating back to StepEditor with glazes:', selected);
navigation.navigate({
name: 'StepEditor',
params: {
projectId: route.params.projectId,
stepId: route.params.stepId,
selectedGlazeIds: selected,
_editorKey: route.params._editorKey, // Same key = same instance!
_timestamp: Date.now(),
},
merge: true, // Merge params with existing screen instead of creating new one
} as any);
};
const renderGlaze = ({ item }: { item: Glaze }) => {
const isSelected = selected.includes(item.id);
return (
<TouchableOpacity onPress={() => toggleGlaze(item.id)}>
<Card style={[styles.glazeCard, isSelected ? styles.glazeCardSelected : null]}>
<View style={styles.glazeMainContent}>
{item.color && (
<View style={[styles.colorPreview, { backgroundColor: item.color }]} />
)}
<View style={styles.glazeInfo}>
<View style={styles.glazeHeader}>
<Text style={styles.glazeBrand}>{item.brand}</Text>
<View style={styles.badges}>
{item.isMix && <Text style={styles.mixBadge}>Mix</Text>}
{item.isCustom && !item.isMix && <Text style={styles.customBadge}>Custom</Text>}
</View>
</View>
<Text style={styles.glazeName}>{item.name}</Text>
{item.code && <Text style={styles.glazeCode}>Code: {item.code}</Text>}
{item.finish && (
<Text style={styles.glazeFinish}>
{item.finish.charAt(0).toUpperCase() + item.finish.slice(1)}
</Text>
)}
{item.mixRatio && (
<Text style={styles.glazeMixRatio}>Ratio: {item.mixRatio}</Text>
)}
</View>
</View>
</Card>
</TouchableOpacity>
);
};
return (
<SafeAreaView style={styles.container} edges={['top']}>
<View style={styles.header}>
<TextInput
style={styles.searchInput}
placeholder="Search glazes..."
value={searchQuery}
onChangeText={setSearchQuery}
placeholderTextColor={colors.textSecondary}
/>
</View>
<Text style={styles.selectedCount}>
Selected: {selected.length} glaze{selected.length !== 1 ? 's' : ''}
</Text>
<FlatList
data={glazes}
renderItem={renderGlaze}
keyExtractor={(item) => item.id}
contentContainerStyle={styles.listContent}
ListEmptyComponent={
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>
{searchQuery ? 'No glazes found' : 'No glazes in catalog'}
</Text>
</View>
}
/>
<View style={styles.footer}>
<Button
title="Mix Glazes"
onPress={() => navigation.navigate('GlazeMixer', {
projectId: route.params.projectId,
stepId: route.params.stepId,
_editorKey: route.params._editorKey, // Pass editor key through
})}
variant="outline"
style={styles.mixButton}
/>
<Button
title="Save Selection"
onPress={handleSave}
disabled={selected.length === 0}
/>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.backgroundSecondary,
},
header: {
paddingHorizontal: spacing.lg,
paddingTop: spacing.md,
paddingBottom: spacing.md,
backgroundColor: colors.background,
},
searchInput: {
backgroundColor: colors.backgroundSecondary,
borderRadius: borderRadius.md,
borderWidth: 2,
borderColor: colors.border,
paddingHorizontal: spacing.md,
paddingVertical: spacing.md,
fontSize: typography.fontSize.md,
color: colors.text,
},
selectedCount: {
paddingHorizontal: spacing.lg,
paddingVertical: spacing.sm,
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.textSecondary,
textTransform: 'uppercase',
},
listContent: {
paddingHorizontal: spacing.md,
paddingBottom: spacing.xl,
},
glazeCard: {
marginBottom: spacing.md,
},
glazeCardSelected: {
borderWidth: 3,
borderColor: colors.primary,
backgroundColor: colors.primaryLight + '20',
},
glazeMainContent: {
flexDirection: 'row',
gap: spacing.md,
},
colorPreview: {
width: 50,
height: 50,
borderRadius: borderRadius.md,
borderWidth: 2,
borderColor: colors.border,
},
glazeInfo: {
flex: 1,
},
glazeHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: spacing.xs,
},
glazeBrand: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.bold,
color: colors.primary,
textTransform: 'uppercase',
letterSpacing: 0.5,
},
badges: {
flexDirection: 'row',
gap: spacing.xs,
},
customBadge: {
fontSize: typography.fontSize.xs,
fontWeight: typography.fontWeight.bold,
color: colors.background,
backgroundColor: colors.info,
paddingHorizontal: spacing.sm,
paddingVertical: spacing.xs,
borderRadius: borderRadius.full,
},
mixBadge: {
fontSize: typography.fontSize.xs,
fontWeight: typography.fontWeight.bold,
color: colors.background,
backgroundColor: colors.primary,
paddingHorizontal: spacing.sm,
paddingVertical: spacing.xs,
borderRadius: borderRadius.full,
},
glazeName: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.bold,
color: colors.text,
marginBottom: spacing.xs,
},
glazeCode: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
},
glazeFinish: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
fontStyle: 'italic',
},
glazeMixRatio: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
fontStyle: 'italic',
},
emptyContainer: {
padding: spacing.xl,
alignItems: 'center',
},
emptyText: {
fontSize: typography.fontSize.md,
color: colors.textSecondary,
},
footer: {
padding: spacing.md,
backgroundColor: colors.background,
borderTopWidth: 2,
borderTopColor: colors.border,
flexDirection: 'row',
gap: spacing.md,
},
mixButton: {
flex: 1,
},
});