Skip to content
Merged
12 changes: 8 additions & 4 deletions formulus/src/components/MenuDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
Modal,
TouchableOpacity,
ScrollView,
SafeAreaView,
} from 'react-native';
import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import {getUserInfo, UserInfo, UserRole} from '../api/synkronus/Auth';

Expand Down Expand Up @@ -48,6 +48,9 @@ const MenuDrawer: React.FC<MenuDrawerProps> = ({
allowClose = true,
}) => {
const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
const insets = useSafeAreaInsets();
const TAB_BAR_HEIGHT = 60;
const bottomPadding = TAB_BAR_HEIGHT + insets.bottom;

useEffect(() => {
if (visible) {
Expand Down Expand Up @@ -109,8 +112,10 @@ const MenuDrawer: React.FC<MenuDrawerProps> = ({
onPress={onClose}
/>
)}
<View style={styles.drawer}>
<SafeAreaView style={styles.safeArea}>
<View style={[styles.drawer, {bottom: bottomPadding}]}>
<SafeAreaView
style={styles.safeArea}
edges={['top', 'left', 'right']}>
<View style={styles.header}>
<Text style={styles.headerTitle}>Menu</Text>
{allowClose && (
Expand Down Expand Up @@ -190,7 +195,6 @@ const styles = StyleSheet.create({
position: 'absolute',
right: 0,
top: 0,
bottom: 0,
width: '80%',
maxWidth: 320,
backgroundColor: '#FFFFFF',
Expand Down
66 changes: 66 additions & 0 deletions formulus/src/components/common/EmptyState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import {View, Text, StyleSheet} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import colors from '../../theme/colors';

interface EmptyStateProps {
icon?: string;
title: string;
message: string;
actionLabel?: string;
onAction?: () => void;
}

const EmptyState: React.FC<EmptyStateProps> = ({
icon = 'information-outline',
title,
message,
actionLabel,
onAction,
}) => {
return (
<View style={styles.container}>
<Icon name={icon} size={64} color={colors.neutral[400]} />
<Text style={styles.title}>{title}</Text>
<Text style={styles.message}>{message}</Text>
{actionLabel && onAction && (
<Text style={styles.actionText} onPress={onAction}>
{actionLabel}
</Text>
)}
</View>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 32,
},
title: {
fontSize: 20,
fontWeight: '600',
color: colors.neutral[900],
marginTop: 16,
marginBottom: 8,
textAlign: 'center',
},
message: {
fontSize: 14,
color: colors.neutral[600],
textAlign: 'center',
lineHeight: 20,
marginBottom: 16,
},
actionText: {
fontSize: 16,
color: colors.brand.primary[500],
fontWeight: '500',
marginTop: 8,
},
});

export default EmptyState;

180 changes: 180 additions & 0 deletions formulus/src/components/common/FilterBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import React from 'react';
import {View, Text, TouchableOpacity, StyleSheet, TextInput} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import {SortOption, FilterOption} from './FilterBar.types';

export type {SortOption, FilterOption};

interface FilterBarProps {
searchQuery: string;
onSearchChange: (query: string) => void;
sortOption: SortOption;
onSortChange: (option: SortOption) => void;
filterOption?: FilterOption;
onFilterChange?: (option: FilterOption) => void;
showFilter?: boolean;
}

const FilterBar: React.FC<FilterBarProps> = ({
searchQuery,
onSearchChange,
sortOption,
onSortChange,
filterOption = 'all',
onFilterChange,
showFilter = false,
}) => {
const sortOptions: {value: SortOption; label: string}[] = [
{value: 'date-desc', label: 'Newest'},
{value: 'date-asc', label: 'Oldest'},
{value: 'form-type', label: 'Form Type'},
{value: 'sync-status', label: 'Sync Status'},
];

const filterOptions: {value: FilterOption; label: string}[] = [
{value: 'all', label: 'All'},
{value: 'synced', label: 'Synced'},
{value: 'pending', label: 'Pending'},
];

return (
<View style={styles.container}>
<View style={styles.searchContainer}>
<Icon name="magnify" size={20} color="#999" style={styles.searchIcon} />
<TextInput
style={styles.searchInput}
placeholder="Search..."
placeholderTextColor="#999"
value={searchQuery}
onChangeText={onSearchChange}
/>
{searchQuery.length > 0 && (
<TouchableOpacity onPress={() => onSearchChange('')}>
<Icon name="close-circle" size={20} color="#999" />
</TouchableOpacity>
)}
</View>

<View style={styles.controlsRow}>
<View style={styles.sortContainer}>
<Text style={styles.label}>Sort:</Text>
{sortOptions.map(option => (
<TouchableOpacity
key={option.value}
style={[
styles.optionButton,
sortOption === option.value && styles.optionButtonActive,
]}
onPress={() => onSortChange(option.value)}
>
<Text
style={[
styles.optionText,
sortOption === option.value && styles.optionTextActive,
]}
>
{option.label}
</Text>
</TouchableOpacity>
))}
</View>

{showFilter && onFilterChange && (
<View style={styles.filterContainer}>
<Text style={styles.label}>Filter:</Text>
{filterOptions.map(option => (
<TouchableOpacity
key={option.value}
style={[
styles.optionButton,
filterOption === option.value && styles.optionButtonActive,
]}
onPress={() => onFilterChange(option.value)}
>
<Text
style={[
styles.optionText,
filterOption === option.value && styles.optionTextActive,
]}
>
{option.label}
</Text>
</TouchableOpacity>
))}
</View>
)}
</View>
</View>
);
};

const styles = StyleSheet.create({
container: {
backgroundColor: '#FFFFFF',
padding: 12,
borderBottomWidth: 1,
borderBottomColor: '#E5E5E5',
},
searchContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#F5F5F5',
borderRadius: 8,
paddingHorizontal: 12,
marginBottom: 12,
},
searchIcon: {
marginRight: 8,
},
searchInput: {
flex: 1,
fontSize: 14,
color: '#333',
paddingVertical: 8,
},
controlsRow: {
flexDirection: 'row',
justifyContent: 'space-between',
flexWrap: 'wrap',
},
sortContainer: {
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap',
flex: 1,
},
filterContainer: {
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap',
flex: 1,
},
label: {
fontSize: 12,
color: '#666',
marginRight: 8,
fontWeight: '500',
},
optionButton: {
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 12,
backgroundColor: '#F5F5F5',
marginRight: 6,
marginBottom: 4,
},
optionButtonActive: {
backgroundColor: '#007AFF',
},
optionText: {
fontSize: 12,
color: '#666',
fontWeight: '500',
},
optionTextActive: {
color: '#FFFFFF',
},
});

export default FilterBar;

3 changes: 3 additions & 0 deletions formulus/src/components/common/FilterBar.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type SortOption = 'date-desc' | 'date-asc' | 'form-type' | 'sync-status';
export type FilterOption = 'all' | 'synced' | 'pending';

Loading