|
@@ -0,0 +1,276 @@
|
|
|
+import React, { useCallback, useEffect, useState } from 'react';
|
|
|
+import { useFocusEffect } from '@react-navigation/native';
|
|
|
+import { View, Text, FlatList } from 'react-native';
|
|
|
+import { Button, Header, Input, Loading, Modal, PageWrapper } from 'src/components';
|
|
|
+import { TabView, TabBar } from 'react-native-tab-view';
|
|
|
+
|
|
|
+import SearchIcon from '../../../../../assets/icons/search.svg';
|
|
|
+import { fetchItemsForSeries, usePostSetToggleItem } from '@api/series';
|
|
|
+import { Colors } from 'src/theme';
|
|
|
+import { ButtonVariants } from 'src/types/components';
|
|
|
+import { AccordionListItem } from '../Components/AccordionListItem';
|
|
|
+
|
|
|
+import { styles } from './styles';
|
|
|
+
|
|
|
+interface SeriesItem {
|
|
|
+ series_id: number;
|
|
|
+ item_id: number;
|
|
|
+ icon: string | null;
|
|
|
+ name: string;
|
|
|
+ readonly: boolean;
|
|
|
+ info: string;
|
|
|
+ new: boolean;
|
|
|
+ checked: boolean;
|
|
|
+ checked_double: boolean;
|
|
|
+ double: boolean;
|
|
|
+}
|
|
|
+
|
|
|
+interface SeriesGroup {
|
|
|
+ name: string;
|
|
|
+ icon: string;
|
|
|
+ series_id: number;
|
|
|
+ series_name: string;
|
|
|
+ items: SeriesItem[];
|
|
|
+}
|
|
|
+
|
|
|
+interface FilteredData {
|
|
|
+ [key: string]: SeriesGroup[];
|
|
|
+}
|
|
|
+
|
|
|
+interface Route {
|
|
|
+ key: string;
|
|
|
+ title: string;
|
|
|
+}
|
|
|
+
|
|
|
+export const SeriesItemScreen = ({ route }: { route: any }) => {
|
|
|
+ const { id, name, token } = route.params;
|
|
|
+ const { mutate: updateSeriesItem } = usePostSetToggleItem();
|
|
|
+
|
|
|
+ const [search, setSearch] = useState<string>('');
|
|
|
+ const [filteredData, setFilteredData] = useState<FilteredData>({});
|
|
|
+ const [seriesData, setSeriesData] = useState<SeriesGroup[]>([]);
|
|
|
+ const [index, setIndex] = useState<number>(0);
|
|
|
+ const [isLoading, setIsLoading] = useState<boolean>(true);
|
|
|
+ const [isInfoModalVisible, setIsInfoModalVisible] = useState<boolean>(false);
|
|
|
+ const [infoItem, setInfoItem] = useState<SeriesItem | null>(null);
|
|
|
+ const [activeFilteredData, setActiveFilteredData] = useState<SeriesGroup[]>([]);
|
|
|
+
|
|
|
+ useFocusEffect(
|
|
|
+ useCallback(() => {
|
|
|
+ const fetchGroups = async () => {
|
|
|
+ const data = await fetchItemsForSeries(token, id);
|
|
|
+ if (!data) {
|
|
|
+ setIsLoading(false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const allItems = data.groups;
|
|
|
+ const newItems = data.groups
|
|
|
+ .map((group) => ({
|
|
|
+ ...group,
|
|
|
+ items: group.items.filter((item) => item.new)
|
|
|
+ }))
|
|
|
+ .filter((group) => group.items.length > 0);
|
|
|
+
|
|
|
+ setFilteredData({ all: allItems, new: newItems, unchecked: [], checked: [] });
|
|
|
+ setSeriesData(data.groups);
|
|
|
+ setIsLoading(false);
|
|
|
+ };
|
|
|
+
|
|
|
+ fetchGroups();
|
|
|
+ }, [])
|
|
|
+ );
|
|
|
+
|
|
|
+ const [routes] = useState([
|
|
|
+ { key: 'all', title: 'All items' },
|
|
|
+ { key: 'new', title: 'New' },
|
|
|
+ { key: 'unchecked', title: 'Unchecked' },
|
|
|
+ { key: 'checked', title: 'Checked' }
|
|
|
+ ]);
|
|
|
+
|
|
|
+ const handleIndexChange = (index: number) => {
|
|
|
+ let dataToFilter = [...seriesData];
|
|
|
+
|
|
|
+ switch (index) {
|
|
|
+ case 1:
|
|
|
+ dataToFilter = filteredData[routes[index].key];
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ dataToFilter = dataToFilter
|
|
|
+ .map((group) => ({
|
|
|
+ ...group,
|
|
|
+ items: group.items.filter((item) => !item.checked)
|
|
|
+ }))
|
|
|
+ .filter((group) => group.items.length > 0);
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ dataToFilter = dataToFilter
|
|
|
+ .map((group) => ({
|
|
|
+ ...group,
|
|
|
+ items: group.items.filter((item) => item.checked)
|
|
|
+ }))
|
|
|
+ .filter((group) => group.items.length > 0);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ setActiveFilteredData(dataToFilter);
|
|
|
+ setFilteredData((prevState) => ({
|
|
|
+ ...prevState,
|
|
|
+ [routes[index].key]: dataToFilter
|
|
|
+ }));
|
|
|
+ };
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ handleIndexChange(index);
|
|
|
+ }, [seriesData]);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ setActiveFilteredData(filteredData[routes[index].key]);
|
|
|
+ }, [filteredData]);
|
|
|
+
|
|
|
+ const renderScene = ({ route }: { route: Route }) => {
|
|
|
+ return isLoading ? (
|
|
|
+ <Loading />
|
|
|
+ ) : (
|
|
|
+ <FlatList
|
|
|
+ key={routes[index].key}
|
|
|
+ keyExtractor={(item, index) => index.toString()}
|
|
|
+ showsVerticalScrollIndicator={false}
|
|
|
+ initialNumToRender={15}
|
|
|
+ style={{ paddingTop: 10 }}
|
|
|
+ data={activeFilteredData}
|
|
|
+ renderItem={({ item }) => (
|
|
|
+ <AccordionListItem
|
|
|
+ item={item}
|
|
|
+ onCheckboxChange={handleCheckboxChange}
|
|
|
+ setIsInfoModalVisible={setIsInfoModalVisible}
|
|
|
+ setInfoItem={setInfoItem}
|
|
|
+ isSeries={id === -1}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ const searchText = search.toLowerCase();
|
|
|
+ const searchData = filteredData[routes[index].key]
|
|
|
+ ?.map((group) => {
|
|
|
+ const groupNameMatch = group.name.toLowerCase().includes(searchText);
|
|
|
+ if (groupNameMatch) {
|
|
|
+ return group;
|
|
|
+ } else {
|
|
|
+ const filteredItems = group.items.filter((item) =>
|
|
|
+ item.name.toLowerCase().includes(searchText)
|
|
|
+ );
|
|
|
+ return filteredItems.length > 0 ? { ...group, items: filteredItems } : null;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .filter((group) => group !== null);
|
|
|
+
|
|
|
+ setActiveFilteredData(searchData as SeriesGroup[]);
|
|
|
+ }, [search]);
|
|
|
+
|
|
|
+ const handleCheckboxChange = useCallback(
|
|
|
+ async (item: SeriesItem, groupName: string, double?: boolean) => {
|
|
|
+ setSeriesData((currentData) => {
|
|
|
+ const groupIndex = currentData.findIndex((group) => group.name === groupName);
|
|
|
+
|
|
|
+ if (groupIndex === -1) return currentData;
|
|
|
+
|
|
|
+ const newData = [...currentData];
|
|
|
+ const newGroup = { ...newData[groupIndex] };
|
|
|
+
|
|
|
+ newGroup.items = newGroup.items.map((subItem) =>
|
|
|
+ subItem.item_id === item.item_id
|
|
|
+ ? {
|
|
|
+ ...subItem,
|
|
|
+ ...(double
|
|
|
+ ? { checked_double: !subItem.checked_double }
|
|
|
+ : { checked: !subItem.checked })
|
|
|
+ }
|
|
|
+ : subItem
|
|
|
+ );
|
|
|
+
|
|
|
+ newData[groupIndex] = newGroup;
|
|
|
+
|
|
|
+ return newData;
|
|
|
+ });
|
|
|
+
|
|
|
+ const itemData = {
|
|
|
+ token: token,
|
|
|
+ series_id: item.series_id,
|
|
|
+ item_id: item.item_id,
|
|
|
+ checked: (!double ? Number(!item.checked) : Number(item.checked)) as 0 | 1,
|
|
|
+ double: (double ? Number(!item.checked_double) : Number(item.checked_double)) as 0 | 1
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ updateSeriesItem(itemData);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Failed to update checkbox state', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ [token, updateSeriesItem]
|
|
|
+ );
|
|
|
+
|
|
|
+ return (
|
|
|
+ <PageWrapper>
|
|
|
+ <Header label={name} />
|
|
|
+ <Modal
|
|
|
+ visible={isInfoModalVisible}
|
|
|
+ children={
|
|
|
+ <View style={styles.modalView}>
|
|
|
+ <Text style={styles.infoTitle}>{infoItem?.name}</Text>
|
|
|
+ <Text style={styles.infoText}>{infoItem?.info}</Text>
|
|
|
+ <Button
|
|
|
+ variant={ButtonVariants.OPACITY}
|
|
|
+ containerStyles={styles.btnContainer}
|
|
|
+ textStyles={{
|
|
|
+ color: Colors.DARK_BLUE
|
|
|
+ }}
|
|
|
+ onPress={() => setIsInfoModalVisible(false)}
|
|
|
+ children={'Got it'}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+ }
|
|
|
+ onRequestClose={() => setIsInfoModalVisible(false)}
|
|
|
+ headerTitle={'Info'}
|
|
|
+ visibleInPercent={'auto'}
|
|
|
+ />
|
|
|
+ <Input
|
|
|
+ inputMode={'search'}
|
|
|
+ placeholder={'Search'}
|
|
|
+ onChange={(text) => setSearch(text)}
|
|
|
+ value={search}
|
|
|
+ icon={<SearchIcon fill={'#C8C8C8'} width={14} height={14} />}
|
|
|
+ />
|
|
|
+ <TabView
|
|
|
+ navigationState={{ index, routes }}
|
|
|
+ renderScene={renderScene}
|
|
|
+ onIndexChange={(i) => {
|
|
|
+ handleIndexChange(i);
|
|
|
+ setIndex(i);
|
|
|
+ }}
|
|
|
+ lazy={true}
|
|
|
+ renderTabBar={(props) => (
|
|
|
+ <TabBar
|
|
|
+ {...props}
|
|
|
+ indicatorStyle={{ backgroundColor: Colors.DARK_BLUE }}
|
|
|
+ style={styles.tabBar}
|
|
|
+ tabStyle={styles.tabStyle}
|
|
|
+ pressColor={'transparent'}
|
|
|
+ renderLabel={({ route, focused }) => (
|
|
|
+ <Text
|
|
|
+ style={[styles.tabLabel, { color: Colors.DARK_BLUE, opacity: focused ? 1 : 0.4 }]}
|
|
|
+ >
|
|
|
+ {route.title}
|
|
|
+ </Text>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ </PageWrapper>
|
|
|
+ );
|
|
|
+};
|