top-tran/toptran-app/src/components/Select.tsx
Rayan Konecny fea50d5064 Refactors data models and sync, adds company/rides fetch APIs
Unifies naming conventions for users, companies, and rides across
backend and mobile app, standardizing field names for consistency
and easier data interchange.

Introduces endpoints to fetch all companies and rides for sync.
Enhances sync logic to support both upload and download of companies,
including first-time population from server.

Updates local database schema and access logic to match backend
structure, improving maintainability and reliability of sync.
2026-05-03 14:15:37 -03:00

177 lines
4.3 KiB
TypeScript

import { BORDER_RADIUS, COLORS, SPACING } from "@/constants/theme";
import React, { useState } from "react";
import {
FlatList,
Modal,
StyleSheet,
Text,
TouchableOpacity,
View,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
type SelectProps = {
label?: string;
value: string;
onValueChange: (value: string) => void;
items: { label: string; value: string }[];
};
export function Select({ label, value, onValueChange, items }: SelectProps) {
const [open, setOpen] = useState(false);
const selected = items.find((i) => i.value === value);
return (
<View style={styles.container}>
{label && <Text style={styles.label}>{label}</Text>}
<TouchableOpacity
style={styles.trigger}
onPress={() => setOpen(true)}
activeOpacity={0.7}
>
<Text
style={[
styles.triggerText,
!selected?.value && styles.triggerPlaceholder,
]}
numberOfLines={1}
>
{selected?.label ?? "Selecione"}
</Text>
<Text style={styles.arrow}></Text>
</TouchableOpacity>
<Modal visible={open} transparent animationType="fade">
<TouchableOpacity
style={styles.backdrop}
activeOpacity={1}
onPress={() => setOpen(false)}
>
<SafeAreaView style={styles.sheet} onStartShouldSetResponder={() => true}>
<View style={styles.sheetHandle} />
<FlatList
data={items}
keyExtractor={(item) => item.value}
renderItem={({ item }) => (
<TouchableOpacity
style={[
styles.option,
item.value === value && styles.optionSelected,
]}
onPress={() => {
onValueChange(item.value);
setOpen(false);
}}
activeOpacity={0.7}
>
<Text
style={[
styles.optionText,
item.value === value && styles.optionTextSelected,
!item.value && styles.optionPlaceholder,
]}
>
{item.label}
</Text>
{item.value === value && (
<Text style={styles.checkmark}></Text>
)}
</TouchableOpacity>
)}
/>
</SafeAreaView>
</TouchableOpacity>
</Modal>
</View>
);
}
const styles = StyleSheet.create({
container: {
width: "100%",
},
label: {
color: COLORS.text,
fontSize: 14,
fontWeight: "600",
marginBottom: SPACING.sm,
},
trigger: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
height: 48,
borderWidth: 1,
borderColor: COLORS.inputBorder,
backgroundColor: COLORS.inputBackground,
borderRadius: BORDER_RADIUS.md,
paddingHorizontal: SPACING.md,
},
triggerText: {
flex: 1,
fontSize: 15,
color: COLORS.inputText,
},
triggerPlaceholder: {
color: COLORS.inputPlaceholder,
},
arrow: {
fontSize: 14,
color: COLORS.textSecondary,
marginLeft: SPACING.sm,
},
// Modal
backdrop: {
flex: 1,
backgroundColor: "rgba(0,0,0,0.6)",
justifyContent: "flex-end",
},
sheet: {
backgroundColor: COLORS.surface,
borderTopLeftRadius: BORDER_RADIUS.xl,
borderTopRightRadius: BORDER_RADIUS.xl,
maxHeight: "60%",
paddingBottom: SPACING.lg,
},
sheetHandle: {
width: 40,
height: 4,
backgroundColor: COLORS.border,
borderRadius: 2,
alignSelf: "center",
marginTop: SPACING.md,
marginBottom: SPACING.sm,
},
option: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingVertical: SPACING.md,
paddingHorizontal: SPACING.lg,
borderBottomWidth: 1,
borderBottomColor: COLORS.border,
},
optionSelected: {
backgroundColor: COLORS.surfaceLight,
},
optionText: {
fontSize: 15,
color: COLORS.text,
flex: 1,
},
optionTextSelected: {
fontWeight: "700",
color: COLORS.success,
},
optionPlaceholder: {
color: COLORS.textTertiary,
},
checkmark: {
fontSize: 16,
color: COLORS.success,
fontWeight: "700",
},
});