Skip to content

Commit

Permalink
added debounced search to web and mobile (#1797)
Browse files Browse the repository at this point in the history
* added debounced search to web

* added debounced search to mobile app
  • Loading branch information
desperado1802 authored Nov 15, 2023
1 parent 618c854 commit fcc33dd
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
/* eslint-disable react-native/no-unused-styles */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react-native/no-inline-styles */
import React, { FC, useEffect, useState } from 'react';
import moment from 'moment-timezone';
import React, { FC, useCallback, useEffect, useState } from "react"
import moment from "moment-timezone"
import {
View,
Text,
Expand All @@ -15,214 +15,236 @@ import {
TouchableWithoutFeedback,
Pressable,

Check warning on line 16 in apps/mobile/app/screens/Authenticated/SettingScreen/components/TimezonePopup.tsx

View workflow job for this annotation

GitHub Actions / Cspell

Unknown word (Pressable)
FlatList,
TextInput
} from 'react-native';
import { AntDesign, FontAwesome, EvilIcons } from '@expo/vector-icons';
TextInput,
} from "react-native"
import { AntDesign, FontAwesome, EvilIcons } from "@expo/vector-icons"
import _debounce from "lodash/debounce"

// COMPONENTS
import { translate } from '../../../../i18n';
import { typography, useAppTheme } from '../../../../theme';
import { translate } from "../../../../i18n"
import { typography, useAppTheme } from "../../../../theme"

export interface Props {
visible: boolean;
onDismiss: () => unknown;
onTimezoneSelect: (s: string) => unknown;
userTimezone: string;
visible: boolean
onDismiss: () => unknown
onTimezoneSelect: (s: string) => unknown
userTimezone: string
}

export interface IFilter {}

const { height } = Dimensions.get('window');
const { height } = Dimensions.get("window")
const ModalPopUp = ({ visible, children, onDismiss }) => {
const [showModal, setShowModal] = React.useState(visible);
const scaleValue = React.useRef(new Animated.Value(0)).current;
const [showModal, setShowModal] = React.useState(visible)
const scaleValue = React.useRef(new Animated.Value(0)).current

React.useEffect(() => {
toggleModal();
}, [visible]);
toggleModal()
}, [visible])
const toggleModal = () => {
if (visible) {
setShowModal(true);
setShowModal(true)
Animated.spring(scaleValue, {
toValue: 1,
useNativeDriver: true
}).start();
useNativeDriver: true,
}).start()
} else {
setTimeout(() => setShowModal(false), 200);
setTimeout(() => setShowModal(false), 200)
Animated.timing(scaleValue, {
toValue: 0,
duration: 300,
useNativeDriver: true
}).start();
useNativeDriver: true,
}).start()
}
};
}
return (
<Modal animationType="fade" transparent visible={showModal}>
<TouchableWithoutFeedback onPress={() => onDismiss()}>
<View style={$modalBackGround}>
<Animated.View style={{ transform: [{ scale: scaleValue }] }}>{children}</Animated.View>
<Animated.View style={{ transform: [{ scale: scaleValue }] }}>
{children}
</Animated.View>
</View>
</TouchableWithoutFeedback>
</Modal>
);
};
)
}

const TimezonePopup: FC<Props> = function FilterPopup({ visible, onDismiss, onTimezoneSelect, userTimezone }) {
const { colors, dark } = useAppTheme();
const [timezones, setTimezones] = useState([]);
const [selectedTimezone, setSelectedTimezone] = useState('');
const [searchText, setSearchText] = useState<string>('');
const TimezonePopup: FC<Props> = function FilterPopup({
visible,
onDismiss,
onTimezoneSelect,
userTimezone,
}) {
const { colors, dark } = useAppTheme()
const [timezones, setTimezones] = useState([])
const [selectedTimezone, setSelectedTimezone] = useState("")
const [searchText, setSearchText] = useState<string>("")
const [searchDebouncedText, setSearchDebouncedText] = useState<string>("")

useEffect(() => {
const allTimezones = moment.tz.names();
const allTimezones = moment.tz.names()
const timezonesWithUTC = allTimezones.map((item) => {
const offset = moment.tz(item).format('Z');
const formattedItem = item.replace(/_/g, ' ');
return { name: formattedItem, offset }; // Create an object with name and offset
});
const offset = moment.tz(item).format("Z")
const formattedItem = item.replace(/_/g, " ")
return { name: formattedItem, offset } // Create an object with name and offset
})

timezonesWithUTC.sort((a, b) => {
if (a.offset < b.offset) {
return -1;
return -1
}
if (a.offset > b.offset) {
return 1;
return 1
}
return 0;
});
return 0
})

const sortedTimezones = timezonesWithUTC.map((item) => `${item.name} (UTC ${item.offset})`);
const sortedTimezones = timezonesWithUTC.map((item) => `${item.name} (UTC ${item.offset})`)

setTimezones(sortedTimezones);
}, []);
setTimezones(sortedTimezones)
}, [])

const handleTimezoneSelect = (item: string) => {
onTimezoneSelect(item);
setSelectedTimezone(item);
onDismiss();
};
onTimezoneSelect(item)
setSelectedTimezone(item)
onDismiss()
}

const handleSearchChangeDebounced = (text: string) => {
setSearchText(text)
_debounce(() => setSearchDebouncedText(text), 300)()
}

return (
<ModalPopUp visible={visible} onDismiss={onDismiss}>
<TouchableWithoutFeedback>
<View style={[styles.mainContainer, { backgroundColor: colors.background }]}>
<Text style={{ ...styles.mainTitle, color: colors.primary }}>
{translate('settingScreen.changeTimezone.selectTimezoneTitle')}
{translate("settingScreen.changeTimezone.selectTimezoneTitle")}
</Text>
<View
style={{
paddingHorizontal: 20
paddingHorizontal: 20,
}}
>
<TextInput
placeholder="Search Time Zone"
placeholderTextColor={dark ? '#5a5b5e' : '#00000021'}
placeholderTextColor={dark ? "#5a5b5e" : "#00000021"}
style={[
styles.textInput,
{
borderBottomColor: dark ? '#5a5b5e' : '#00000021',
borderBottomColor: dark ? "#5a5b5e" : "#00000021",
borderBottomWidth: 1,
color: colors.primary
}
color: colors.primary,
},
]}
value={searchText}
onChangeText={(text) => setSearchText(text)}
onChangeText={(text) => handleSearchChangeDebounced(text)}
/>
</View>

<FlatList
style={styles.listContainer}
bounces={false}
data={timezones.filter((timezone) => timezone.toLowerCase().includes(searchText.toLowerCase()))}
data={timezones.filter((timezone) =>
timezone.toLowerCase().includes(searchDebouncedText.toLowerCase()),
)}
keyExtractor={(item, index) => item.toString()}
initialNumToRender={7}
renderItem={({ item }) => (
<Pressable

Check warning on line 156 in apps/mobile/app/screens/Authenticated/SettingScreen/components/TimezonePopup.tsx

View workflow job for this annotation

GitHub Actions / Cspell

Unknown word (Pressable)
style={{ ...styles.item, borderColor: colors.border }}
onPress={() => handleTimezoneSelect(item)}
>
<Text style={{ ...styles.tzTitle, color: colors.primary }}>{item}</Text>
<Text style={{ ...styles.tzTitle, color: colors.primary }}>
{item}
</Text>
{(selectedTimezone || userTimezone) === item ? (
<AntDesign name="checkcircle" color={'#27AE60'} size={21.5} />
<AntDesign name="checkcircle" color={"#27AE60"} size={21.5} />

Check warning on line 164 in apps/mobile/app/screens/Authenticated/SettingScreen/components/TimezonePopup.tsx

View workflow job for this annotation

GitHub Actions / Cspell

Unknown word (checkcircle)
) : (
<FontAwesome name="circle-thin" size={24} color={dark ? '#5a5b5e' : '#00000021'} />
<FontAwesome
name="circle-thin"
size={24}
color={dark ? "#5a5b5e" : "#00000021"}
/>
)}
</Pressable>

Check warning on line 172 in apps/mobile/app/screens/Authenticated/SettingScreen/components/TimezonePopup.tsx

View workflow job for this annotation

GitHub Actions / Cspell

Unknown word (Pressable)
)}
/>
</View>
</TouchableWithoutFeedback>
</ModalPopUp>
);
};
)
}

export default TimezonePopup;
export default TimezonePopup

const $modalBackGround: ViewStyle = {
flex: 1,
backgroundColor: '#000000AA',
justifyContent: 'center'
};
backgroundColor: "#000000AA",
justifyContent: "center",
}

const styles = StyleSheet.create({
buttonText: {
color: '#FFF',
color: "#FFF",
fontFamily: typography.primary.semiBold,
fontSize: 18
fontSize: 18,
},
item: {
alignItems: 'center',
borderColor: 'rgba(0,0,0,0.13)',
alignItems: "center",
borderColor: "rgba(0,0,0,0.13)",
borderRadius: 10,
borderWidth: 1,
flexDirection: 'row',
flexDirection: "row",
height: 42,
justifyContent: 'space-between',
justifyContent: "space-between",
marginBottom: 10,
paddingHorizontal: 16,
width: '100%'
width: "100%",
},
listContainer: {
marginVertical: 16,
paddingHorizontal: 20,
width: '100%'
width: "100%",
},
mainContainer: {
alignSelf: 'center',
backgroundColor: 'yellow',
borderColor: '#1B005D0D',
alignSelf: "center",
backgroundColor: "yellow",
borderColor: "#1B005D0D",
borderRadius: 24,
borderTopRightRadius: 24,
borderWidth: 2,
height: height / 2,
paddingVertical: 16,
shadowColor: '#1B005D0D',
shadowColor: "#1B005D0D",
shadowOffset: { width: 10, height: 10 },
shadowRadius: 10,
width: '90%',
zIndex: 1000
width: "90%",
zIndex: 1000,
},
mainTitle: {
fontFamily: typography.primary.semiBold,
fontSize: 24,
marginBottom: 10,
paddingHorizontal: 20
paddingHorizontal: 20,
},
statusContainer: {
height: 57,
paddingHorizontal: 16,
paddingVertical: 10,
width: 156,
zIndex: 1000
zIndex: 1000,
},
textInput: { fontSize: 18, marginTop: 10 },
tzTitle: {
fontFamily: typography.primary.semiBold,
fontSize: 14
fontSize: 14,
},
wrapForm: {
marginTop: 16,
width: '100%',
zIndex: 100
}
});
width: "100%",
zIndex: 100,
},
})
2 changes: 1 addition & 1 deletion apps/web/lib/components/dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type Props<T extends DropdownItem> = {
closeOnChildrenClick?: boolean;
cardClassName?: string;
searchBar?: boolean;
setSearchText?: React.Dispatch<SetStateAction<string>>;
setSearchText?: React.Dispatch<React.SetStateAction<string>> | ((e: string) => void);
} & PropsWithChildren;

export function Dropdown<T extends DropdownItem>({
Expand Down
23 changes: 21 additions & 2 deletions apps/web/lib/features/task/task-filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -406,12 +406,31 @@ function TaskNameFilter({
close: () => void;
}) {
const { t } = useTranslation();

const [typingTimeout, setTypingTimeout] = useState<NodeJS.Timeout | null>(null);
const [tempValue, setTempValue] = useState<string>('');

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const inputValue = e.target.value;
setTempValue(inputValue);

if (typingTimeout) {
clearTimeout(typingTimeout);
}

const newTimeout = setTimeout(() => {
setValue(inputValue);
}, 300);

setTypingTimeout(newTimeout);
};

return (
<div className="flex flex-row w-1/2 gap-2 mt-3 ml-auto">
<InputField
value={value}
value={tempValue}
autoFocus={true}
onChange={(e) => setValue(e.target.value)}
onChange={(e) => handleInputChange(e)}
placeholder={t('common.TYPE_SOMETHING') + '...'}
wrapperClassName="mb-0 dark:bg-transparent"
/>
Expand Down
Loading

0 comments on commit fcc33dd

Please sign in to comment.