Projekt

Obecné

Profil

« Předchozí | Další » 

Revize af8eb9a6

Přidáno uživatelem Schwobik před téměř 2 roky(ů)

Implementation of multiselect component, search page finished with listview
re #10478

Zobrazit rozdíly:

package.json
22 22
    "react-native": "0.71.6",
23 23
    "react-native-deck-swiper": "^2.0.13",
24 24
    "react-native-gesture-handler": "~2.9.0",
25
    "react-native-logs": "^5.0.1",
25 26
    "react-native-multiple-select": "^0.5.12",
26 27
    "react-native-reanimated": "~2.14.4",
27 28
    "react-native-safe-area-context": "4.5.0",
src/api/searchService.ts
1 1
import { axiosInstance } from "./api"
2
import { log } from "../logging/logger"
2 3

  
3 4
export interface SearchParams {
4
    inventory?: string
5
    inventories?: string[]
6
    rooms?: string[]
7
    artists?: string[]
8
    nationalities?: string[]
9
    subjects?: string[]
10
    techniques?: string[]
11
    isSchoolOfPrague?: boolean
12
    isOriginal?: boolean
13
    isCopy?: boolean
14
    isLowQuality?: boolean
15
    isHighQuality?: boolean
16
    isIdentified?: boolean
17
    institutions?: string[]
18
    cities?: string[]
19
    countries?: string[]
20
    searchQuery?: string
5 21
    //TODO: add other search params
6 22
}
7 23

  
24
const composeSearchParams = (array: string[] | undefined, identifier: string) => {
25
    log.debug("composeSearchParams", array, identifier)
26

  
27
    if (array && array.length > 0) {
28
        return array.map((item) => `&${identifier}=${item}`).join("")
29
    }
30
    return ""
31
}
32

  
8 33
export const searchRequest = async (params: SearchParams) => {
9
    return await axiosInstance.get(`/search_v2${ (params.inventory ? `?inventory=${ params.inventory }` : "")}`)
34
    const url = "/search_v2"
35
    + "?search=" + (params.searchQuery ? params.searchQuery : "")
36
    + composeSearchParams(params.inventories, "inventory")
37
    + composeSearchParams(params.rooms, "room")
38
    + composeSearchParams(params.artists, "persname")
39
    + composeSearchParams(params.nationalities, "nationality")
40
    // TODO add other search params
41
    log.debug("searchRequest url: " + url)
42
    return await axiosInstance.get(url)
43

  
10 44
}
src/components/Navigation.tsx
20 20
            <Drawer.Navigator useLegacyImplementation
21 21
                screenOptions={({ navigation }) => ({
22 22
                    headerStyle: {
23
                        backgroundColor: nativeBaseTheme.colors.primary[50],
23
                        backgroundColor: nativeBaseTheme.colors.primary[800],
24 24
                    },
25 25
                    headerTintColor: '#fff',
26 26
                    headerTitleStyle: {
src/components/listView/ItemPreview.tsx
1 1
import { Center, HStack, Image, Text, VStack } from "native-base"
2
import { useEffect } from "react"
2 3

  
3 4
interface ItemPreviewProps {
4 5
    caption: string
......
9 10

  
10 11
const ItemPreview = (props: ItemPreviewProps) => {
11 12

  
13
    useEffect(() => {
14
        console.log("ItemPreview", "Props:", props)
15
    }, [props])
16

  
12 17
    return (
13 18
        <>
14

  
15 19
            <HStack
16 20
                space={ 2 }
17 21
                flex={ 1 }
18 22
                alignItems={ "start" }
19 23
            >
20 24
                <Image
21
                    source={ {uri: `http:/147.228.173.159/static/images/${ props.image ? props.image : "thumb-Rel-78.png" }`} }
25
                    source={ {uri: `http:/147.228.173.159/static/images/${ props.image ? "thumb-" + props.image : "thumb-Rel-78.png" }`} }
22 26
                    size={ "sm" }
23 27
                    alt={ props.image ? props.image : "thumb-Rel-78.png" }
24 28
                />
25
                <VStack>
26
                    <Text>{ props.name }</Text>
27
                    <Text>{ props.caption }</Text>
28
                    <Text>{ props.title }</Text>
29
                <VStack h={70}>
30
                    <Text fontSize={"sm"} italic>{ props.name }</Text>
31
                    <Text fontSize={"sm"} bold>{ props.caption }</Text>
32
                    <Text fontSize={"xs"}>{ props.title }</Text>
29 33
                </VStack>
30 34
            </HStack>
31 35
        </>
src/components/listView/ListView.tsx
3 3
import { useSelector } from "react-redux"
4 4
import { RootState } from "../../stores/store"
5 5
import ItemPreview from "./ItemPreview"
6
import { ItemPreviewType } from "../../types/listViewTypes"
7
import { log } from "../../logging/logger"
8

  
6 9

  
7 10
const ListView = () => {
8 11
    const items = useSelector((state: RootState) => state.listView.data)
9 12

  
13
    /**
14
     * Searches through names by given role, return getty_data.display_name if exists otherwise name.value
15
     * @param item
16
     * @param role
17
     */
18
    const parseNameByRole = (item: ItemPreviewType, role: string) => {
19
        // @ts-ignore
20
        const name = item.object[0].name?.find((name) => name.role === role)
21
        return name && name ?
22
            name.getty_data ?
23
                name.getty_data.display_name : name.value
24
            : ""
25
    }
26

  
27
    /**
28
     * Returns artist if it is original, otherwise returns copist and original artist in copy of
29
     * @param item
30
     */
31
    const parseArtists = (item: ItemPreviewType) => {
32
        // if is copy - display copist and copy of original artist if they are present in data
33
        if (item.src?.value && item.src?.value == "copy") {
34
            const copist = parseNameByRole(item, "copist")
35
            const artist = parseNameByRole(item, "artist")
36
            return `${ copist } ${ artist !== "" ? `(copy of ${ artist })` : "" }`
37
        } else {
38
            // if is original - display artist
39
            return parseNameByRole(item, "artist")
40
        }
41
    }
42

  
43
    /**
44
     * Parses image name
45
     * cycles through all objects and returns first image name with changed suffix to .png of undefined if not found
46
     * @param item
47
     */
48
    const parseImage = (item: ItemPreviewType) => {
49
        // @ts-ignore
50
        const image = item.object.find((object) => object.images)
51
        // @ts-ignore
52
        return image && image.images ? image.images[0].file.split('.')[0] + '.png' : undefined
53
    }
54

  
55
    useEffect(() => {
56
        log.debug("ListView", "Items:", items)
57
    }, [items])
58

  
10 59
    return (
11
        <VStack
12
            space={ 2 }
13
            flex={ 1 }
14
            p={ 4 }
15
            alignItems={ "start" }
16
        >
60
        <>
17 61
            { items.map((item) => (
18 62
                <ItemPreview
63
                    // @ts-ignore
19 64
                    caption={ item.object[0].caption }
20 65
                    title={ item.text }
21
                    name={
22
                        item.object[0].name ?
23
                            item.object[0].name[0].getty_data ?
24
                                item.object[0].name[0].getty_data.display_name
25
                                :
26
                                item.object[0].name[0].value
27
                            :
28
                            undefined
29
                    }
30
                    image={ item.object[1] && item.object[1].images && item.object[1].images ? item.object[1].images[0].file : undefined }
66
                    name={ parseArtists(item) }
67
                    image={ parseImage(item) }
31 68
                />
32 69
            )) }
33
        </VStack>
70
        </>
34 71
    )
35 72
}
36 73

  
src/components/search/MultiSelect.tsx
1
import {
2
    Actionsheet, Badge,
3
    Box, Button, CloseIcon, Divider, HStack,
4
    Icon,
5
    Input,
6
    KeyboardAvoidingView,
7
    Pressable,
8
    ScrollView, SearchIcon, Spinner,
9
    Text,
10
    useDisclose, View
11
} from "native-base"
12
import { FlatList, GestureResponderEvent, Platform } from "react-native"
13
import { useEffect, useMemo, useState } from "react"
14
import { Ionicons } from "@expo/vector-icons"
15
import { log } from "../../logging/logger"
16

  
17
interface MultiSelectProps {
18
    data: { label: string, value: string, index: number }[],
19
    label: string,
20
    selectedItems: { label: string, value: string }[],
21
    onSelectedItemsChange: (selectedItems: { label: string, value: string }[]) => void,
22
}
23

  
24
const MultiSelect = (props: MultiSelectProps) => {
25
    const [isSelected, setIsSelected] = useState(Array(props.data.length).fill(false))
26
    const [query, setQuery] = useState("")
27
    const {
28
        isOpen,
29
        onOpen,
30
        onClose,
31
    } = useDisclose()
32

  
33
    const filteredData = useMemo(() => {
34
        log.debug("Multiselect", "Filtering data")
35
        if (!query || query.trim() == "") {
36
            return props.data
37
        }
38
        log.debug("Multiselect", "Filtering data", "Query:", query)
39
        log.debug("Multiselect", "Filtering data", "Data:", props.data)
40
        return props.data.filter((row) => row.label?.toLowerCase().includes(query.toLowerCase()))
41
    }, [query, props.data])
42

  
43
    const updateSelected = (index: number) => {
44
        isSelected[index] = !isSelected[index]
45
        setIsSelected([...isSelected])
46
    }
47

  
48
    useEffect(() => {
49
        log.debug("Multiselect", "Updating selected items before render")
50

  
51
        const newSelected = Array(props.data.length).fill(false)
52
        props.selectedItems.forEach((item) => {
53
            const index = props.data.findIndex((i) => i.value === item.value)
54
            if (index !== -1) {
55
                newSelected[index] = true
56
            }
57
        })
58
        setIsSelected(newSelected)
59

  
60
        log.debug("Multiselect", "Updated selected items before render")
61
    }, [props.selectedItems])
62

  
63
    const onCancel = () => {
64
        onClose()
65
    }
66

  
67
    const onDone = () => {
68
        log.debug("Multiselect", "Done clicked")
69

  
70
        const selectedItems = props.data.filter((row, index) => isSelected[index]).map((row) => ({
71
            label: row.label,
72
            value: row.value
73
        }))
74
        props.onSelectedItemsChange(selectedItems)
75
        onClose()
76
    }
77

  
78
    const unselectItem = (index: number) => {
79
        log.debug("Multiselect", "Unselecting item")
80

  
81
        props.selectedItems.splice(index, 1)
82
        props.onSelectedItemsChange([...props.selectedItems])
83
    }
84

  
85
    return (
86
        <>
87
            <Pressable
88
                onPress={ onOpen }
89
                rounded="md"
90
                mt={ 2 }
91
            >
92
                <Text>{ props.label }</Text>
93
            </Pressable>
94

  
95
            <Box flex={ 1 }>
96
                <ScrollView horizontal pb={ 2 }>
97
                    <HStack space={ 1 } flexWrap="wrap">
98
                        { props.selectedItems.map((item, index) => (
99
                            <Badge
100
                                endIcon={
101
                                    <CloseIcon
102
                                        size={ 4 }
103
                                        onPress={ () => {
104
                                            unselectItem(index)
105
                                        } }
106
                                    />
107
                                }
108
                                rounded="md"
109
                                key={ item.value }
110
                                variant="solid"
111
                            >{ item.label }</Badge>
112
                        )) }
113
                    </HStack>
114
                </ScrollView>
115
            </Box>
116

  
117

  
118
            <Divider background={ "primary.500" }/>
119
            { isOpen && (
120
                <Actionsheet isOpen={ isOpen } onClose={ onClose }>
121
                    <Actionsheet.Content>
122
                        <KeyboardAvoidingView
123
                            h={ {lg: "auto"} }
124
                            behavior={ "padding" }
125
                            keyboardVerticalOffset={ 160 }
126
                        >
127
                            <Input
128
                                placeholder="Search"
129
                                variant="filled"
130
                                width="100%"
131
                                borderRadius="10"
132
                                py="1"
133
                                px="2"
134
                                value={ query }
135
                                onChangeText={ setQuery }
136
                                InputLeftElement={
137
                                    <SearchIcon
138
                                        ml="2"
139
                                        size="4"
140
                                        color="gray.400"
141
                                    />
142
                                }
143
                                InputRightElement={
144
                                    <CloseIcon
145
                                        mr="2"
146
                                        size="4"
147
                                        color="gray.400"
148
                                        onPress={ () => setQuery("") }
149
                                    />
150
                                }
151
                            />
152
                            <FlatList
153
                                data={ filteredData }
154
                                renderItem={ ({item}) => (
155
                                    <Actionsheet.Item
156
                                        _pressed={ {bg: "primary.100"} }
157
                                        onPress={ () => updateSelected(item.index) }
158
                                        key={ item.value }
159
                                        backgroundColor={ isSelected[item.index] ? "primary.100" : {} }
160
                                    >
161
                                        { item.label }
162
                                    </Actionsheet.Item>
163
                                ) }
164
                                keyExtractor={ item => item.value }
165
                                keyboardShouldPersistTaps={ "always" }
166
                            />
167
                            <HStack justifyContent={ "flex-end" } px={ 3 } space={ 2 }>
168
                                <Button
169
                                    onPress={ onCancel }
170
                                    colorScheme={ "error" }
171
                                    borderRadius={ 10 }
172
                                    variant={ "subtle" }
173
                                    startIcon={
174
                                        <Icon
175
                                            ml="0"
176
                                            size="4"
177
                                            color="error.400"
178
                                            as={ <Ionicons name="ios-close"/> }
179
                                        />
180
                                    }
181
                                >
182
                                    Cancel
183
                                </Button>
184
                                <Button
185
                                    onPress={ onDone }
186
                                    colorScheme={ "primary" }
187
                                    borderRadius={ 10 }
188
                                    variant={ "subtle" }
189
                                    startIcon={
190
                                        <Icon
191
                                            size="4"
192
                                            color="primary.700"
193
                                            as={ <Ionicons name="ios-checkmark"/> }
194
                                        />
195
                                    }
196
                                >
197
                                    Done
198
                                </Button>
199
                            </HStack>
200
                        </KeyboardAvoidingView>
201
                    </Actionsheet.Content>
202
                </Actionsheet>
203
            ) }
204

  
205
        </>
206
    )
207

  
208
}
209

  
210
export default MultiSelect
src/components/search/SearchForm.tsx
1
import { Button, Center, CheckIcon, FormControl, KeyboardAvoidingView, ScrollView, Select, Text } from "native-base"
1
import {
2
    Box,
3
    Button,
4
    CloseIcon,
5
    ScrollView,
6
    VStack
7
} from "native-base"
2 8
import { useDispatch, useSelector } from "react-redux"
3
import React, { useEffect, useState } from "react"
9
import { useEffect, useState } from "react"
4 10
import { AppDispatch, RootState } from "../../stores/store"
5 11
import { Inventory } from "../../types/searchFormTypes"
6
import {
7
    fetchArtists, fetchCities, fetchCountries, fetchInstitutions,
8
    fetchInventories,
9
    fetchNationalities,
10
    fetchPlans,
11
    fetchSubjects
12
} from "../../stores/actions/searchFormThunks"
13
import SearchFormControl from "./SearchFormControl"
14
import { Platform } from "react-native"
15 12
import { search } from "../../stores/actions/listViewThunks"
13
import MultiSelect from "./MultiSelect"
14
import SwitchWithLabel from "./SwitchWithLabel"
15
import { log } from "../../logging/logger"
16 16

  
17
interface SearchFormProps {
18
    isFilterOpen: boolean,
19
}
17 20

  
18
const SearchForm = () => {
21

  
22
const SearchForm = (props: SearchFormProps) => {
19 23
    const inventories: Inventory[] = useSelector((state: RootState) => state.searchForm.inventories)
20 24
    const nationalities = useSelector((state: RootState) => state.searchForm.nationalities)
21 25
    const artists = useSelector((state: RootState) => state.searchForm.artists)
22 26
    const subjects = useSelector((state: RootState) => state.searchForm.subjects)
23 27
    const rooms = useSelector((state: RootState) => state.searchForm.rooms)
28
    const techniques = useSelector((state: RootState) => state.searchForm.techniques)
24 29
    const countries = useSelector((state: RootState) => state.searchForm.countries)
25 30
    const cities = useSelector((state: RootState) => state.searchForm.cities)
26 31
    const institutions = useSelector((state: RootState) => state.searchForm.institutions)
27 32

  
33
    const [selectedInventories, setSelectedInventories] = useState<{ label: string, value: string }[]>([])
34
    const [selectedNationalities, setSelectedNationalities] = useState<{ label: string, value: string }[]>([])
35
    const [selectedArtists, setSelectedArtists] = useState<{ label: string, value: string }[]>([])
36
    const [selectedSubjects, setSelectedSubjects] = useState<{ label: string, value: string }[]>([])
37
    const [selectedRooms, setSelectedRooms] = useState<{ label: string, value: string }[]>([])
38
    const [selectedTechniques, setSelectedTechniques] = useState<{ label: string, value: string }[]>([])
39
    const [selectedCountries, setSelectedCountries] = useState<{ label: string, value: string }[]>([])
40
    const [selectedCities, setSelectedCities] = useState<{ label: string, value: string }[]>([])
41
    const [selectedInstitutions, setSelectedInstitutions] = useState<{ label: string, value: string }[]>([])
28 42

  
29
    const [selectedInventories, setSelectedInventories] = useState<string[]>([])
30
    const [selectedNationalities, setSelectedNationalities] = useState<string[]>([])
31
    const [selectedArtists, setSelectedArtists] = useState<string[]>([])
32
    const [selectedSubjects, setSelectedSubjects] = useState<string[]>([])
33
    const [selectedRooms, setSelectedRooms] = useState<string[]>([])
34
    const [selectedCountries, setSelectedCountries] = useState<string[]>([])
35
    const [selectedCities, setSelectedCities] = useState<string[]>([])
36
    const [selectedInstitutions, setSelectedInstitutions] = useState<string[]>([])
43
    const [isSchoolOfPrague, setIsSchoolOfPrague] = useState(false)
44
    const [isOriginal, setIsOriginal] = useState(false)
45
    const [isCopy, setIsCopy] = useState(false)
46
    const [isHighQuality, setIsHighQuality] = useState(false)
47
    const [isLowQuality, setIsLowQuality] = useState(false)
37 48

  
38 49
    const dispatch = useDispatch<AppDispatch>()
39 50

  
40
    useEffect(() => {
41
        dispatch(fetchInventories())
42
        dispatch(fetchNationalities())
43
        dispatch(fetchArtists())
44
        dispatch(fetchSubjects())
45
        dispatch(fetchPlans())
46
        dispatch(fetchCountries())
47
        dispatch(fetchCities())
48
        dispatch(fetchInstitutions())
49
    }, [dispatch])
51
    const searchSubmit = () => {
52
        dispatch(search({
53
            inventories: selectedInventories.map(i => i.value),
54
            nationalities: selectedNationalities.map(n => n.value),
55
            artists: selectedArtists.map(a => a.value),
56
            subjects: selectedSubjects.map(s => s.value),
57
            rooms: selectedRooms.map(r => r.value),
58
            techniques: selectedTechniques.map(t => t.value),
59
            countries: selectedCountries.map(c => c.value),
60
            cities: selectedCities.map(c => c.value),
61
            institutions: selectedInstitutions.map(i => i.value),
62
            isSchoolOfPrague,
63
            isOriginal,
64
            isCopy,
65
            isHighQuality,
66
            isLowQuality
67
        }))
68
    }
50 69

  
70
    const clearForm = () => {
71
        log.debug("SearchForm", "clearForm")
51 72

  
52
    const searchSubmit = () => {
53
        dispatch(search({inventory: selectedInventories ? selectedInventories[0] : undefined}))
73
        setSelectedInventories([])
74
        setSelectedNationalities([])
75
        setSelectedArtists([])
76
        setSelectedSubjects([])
77
        setSelectedRooms([])
78
        setSelectedTechniques([])
79
        setSelectedCountries([])
80
        setSelectedCities([])
81
        setSelectedInstitutions([])
54 82
    }
55 83

  
56 84
    return (
57
        <Center>
58
            <ScrollView>
59
                <SearchFormControl
60
                    data={ inventories.map(inventory => {
61
                        return {label: inventory.label, value: inventory.name, key: inventory.name}
62
                    }) }
63
                    label="Inventory"
64
                    placeholder="Choose Inventory"
65
                    selectedItems={ selectedInventories }
66
                    onSelectedItemsChange={ setSelectedInventories }
67
                />
68
                <SearchFormControl
69
                    data={ rooms.map(room => {
70
                        return {label: room.label, value: room.id.toString(), key: room.id.toString()}
71
                    }) }
72
                    label="Rooms"
73
                    placeholder="Room..."
74
                    selectedItems={ selectedRooms }
75
                    onSelectedItemsChange={ setSelectedRooms }
76
                />
77
                <SearchFormControl
78
                    data={ artists.map(art => {
79
                        return {label: art.display_name, value: art.display_name, key: art.display_name}
80
                    }) }
81
                    label="Artists/Copyists"
82
                    placeholder="Artist/Copyist..."
83
                    selectedItems={ selectedArtists }
84
                    onSelectedItemsChange={ setSelectedArtists }
85
                />
86
                <SearchFormControl
87
                    data={ nationalities.map(nat => {
88
                        return {label: nat, value: nat, key: nat}
89
                    }) }
90
                    label="Artist`s Origin"
91
                    placeholder="Nationality..."
92
                    selectedItems={ selectedNationalities }
93
                    onSelectedItemsChange={ setSelectedNationalities }
94
                />
95
                <FormControl key={ "lab" }>
96
                    <FormControl.Label>label</FormControl.Label>
97
                    <Select
98
                        placeholder={ "Select" }
99
                        minWidth="100"
100
                        accessibilityLabel="Choose Service"
101
                        _selectedItem={ {
102
                            bg: "teal.600",
103
                            endIcon: <CheckIcon size={ 5 }/>
104
                        } }
105
                        mt="1"
106
                        key={ "props.label" }
107

  
85
        <>
86
            { props.isFilterOpen && (
87
                <>
88
                    <MultiSelect
89
                        data={ inventories.map((inventory, index) => {
90
                            return {label: inventory.label, value: inventory.name, index}
91
                        }) }
92
                        label="Inventory (OR)"
93
                        selectedItems={ selectedInventories }
94
                        onSelectedItemsChange={ setSelectedInventories }
95
                    />
96
                    <MultiSelect
97
                        data={ rooms.map((room, index) => {
98
                            return {label: `${ room.id } - ${ room.label }`, value: room.id.toString(), index}
99
                        }) }
100
                        label="Rooms (OR)"
101
                        selectedItems={ selectedRooms }
102
                        onSelectedItemsChange={ setSelectedRooms }
103
                    />
104
                    <MultiSelect
105
                        data={ artists.map((art, index) => {
106
                            return {label: art.display_name, value: art.getty_id, index}
107
                        }) }
108
                        label="Artists/Copyists (OR)"
109
                        selectedItems={ selectedArtists }
110
                        onSelectedItemsChange={ setSelectedArtists }
111
                    />
112
                    <MultiSelect
113
                        data={ nationalities.map((nat, index) => {
114
                            return {label: nat, value: nat, index}
115
                        }) }
116
                        label="Artist`s Origin (OR)"
117
                        selectedItems={ selectedNationalities }
118
                        onSelectedItemsChange={ setSelectedNationalities }
119
                    />
120
                    <MultiSelect
121
                        data={ subjects.map((subj, index) => {
122
                            return {label: subj, value: subj, index}
123
                        }) }
124
                        label="Subject (AND)"
125
                        selectedItems={ selectedSubjects }
126
                        onSelectedItemsChange={ setSelectedSubjects }
127
                    />
128
                    <MultiSelect
129
                        data={ techniques.map((tech, index) => {
130
                            return {label: tech, value: tech, index}
131
                        }) }
132
                        label="Technique (OR)"
133
                        selectedItems={ selectedTechniques }
134
                        onSelectedItemsChange={ setSelectedTechniques }
135
                    />
136
                    <MultiSelect
137
                        data={ institutions.map((institution, index) => {
138
                            return {label: institution, value: institution, index}
139
                        }) }
140
                        label="Institution (OR)"
141
                        selectedItems={ selectedInstitutions }
142
                        onSelectedItemsChange={ setSelectedInstitutions }
143
                    />
144
                    <MultiSelect
145
                        data={ cities.map((city, index) => {
146
                            return {label: city, value: city, index}
147
                        }) }
148
                        label="City (OR)"
149
                        selectedItems={ selectedCities }
150
                        onSelectedItemsChange={ setSelectedCities }
151
                    />
152
                    <MultiSelect
153
                        data={ countries.map((country, index) => {
154
                            return {label: country, value: country, index}
155
                        }) }
156
                        label="Country (OR)"
157
                        selectedItems={ selectedCountries }
158
                        onSelectedItemsChange={ setSelectedCountries }
159
                    />
160
                    <VStack space={ 2 } mt={ 1 }>
161
                        <SwitchWithLabel label={ "School of Prague" } value={ isSchoolOfPrague }
162
                                         onValueChange={ setIsSchoolOfPrague }/>
163
                        <SwitchWithLabel label={ "Original" } value={ isOriginal } onValueChange={ setIsOriginal }/>
164
                        <SwitchWithLabel label={ "Copy" } value={ isCopy } onValueChange={ setIsCopy }/>
165
                        <SwitchWithLabel label={ "High Quality" } value={ isHighQuality }
166
                                         onValueChange={ setIsHighQuality }/>
167
                        <SwitchWithLabel label={ "Low Quality" } value={ isLowQuality }
168
                                         onValueChange={ setIsLowQuality }/>
169
                    </VStack>
170
                    <Box
171
                        flex={ 1 }
172
                        flexDirection="row"
173
                        justifyContent={ "flex-end" }
174
                        pt={ 2 }
108 175
                    >
109
                        { inventories.map((row) => (
110
                            <Select.Item label={ row.label } value={ row.name } key={ row.name }/>
111
                        )) }
112
                    </Select>
113
                </FormControl>
114
                <Button
115
                    onPress={ () => searchSubmit()}
116
                    colorScheme="primary.100"
117
                >
118
                    Submit
119
                </Button>
120
            </ScrollView>
121

  
122
        </Center>
176
                        <Button
177
                            borderRadius={ 10 }
178
                            startIcon={
179
                                <CloseIcon size="xs"/>
180
                            }
181
                            onPress={ clearForm }
182
                            variant="outline"
183
                            color="primary.500"
184
                            borderColor="primary.500"
185
                            mr={ 2 }
186
                            size={ "sm" }
187
                        >
188
                            Reset
189
                        </Button>
190
                        <Button
191
                            borderRadius={ 10 }
192
                            onPress={ () => searchSubmit() }
193
                            colorScheme="primary"
194
                            background={ "primary.500" }
195
                            variant="solid"
196
                            size={ "sm" }
197
                        >
198
                            Search
199
                        </Button>
200
                    </Box>
201
                </>
202
            ) }
203
        </>
123 204
    )
124 205
}
125 206

  
src/components/search/SearchFormControl.tsx
1
import { CheckIcon, FormControl, Select, View } from "native-base"
2
import MultiSelect from "react-native-multiple-select"
3

  
4
export interface SearchFormProps {
5
    data: { label: string, value: string, key: string }[],
6
    label: string,
7
    placeholder: string,
8
    selectedItems: string[],
9
    onSelectedItemsChange: (selectedItems: string[]) => void,
10
}
11

  
12
const SearchFormControl = (props: SearchFormProps) => {
13
    return (
14
        <View w={350}>
15
            <MultiSelect
16
                items={ props.data }
17
                uniqueKey="key"
18
                onSelectedItemsChange={ props.onSelectedItemsChange }
19
                selectedItems={ props.selectedItems }
20
                selectText={ props.label }
21
                searchInputPlaceholderText={ props.placeholder }
22
                displayKey="label"
23
                submitButtonText={ "Submit" }
24
                onChangeInput={ (text: string) => console.log(text) }
25
                tagRemoveIconColor="#CCC"
26
                tagBorderColor="#CCC"
27
                tagTextColor="#CCC"
28
                selectedItemTextColor="#CCC"
29
                selectedItemIconColor="#CCC"
30
                itemTextColor="#000"
31
                searchInputStyle={ { color: "#CCC" } }
32
                submitButtonColor="#CCC"
33

  
34
            />
35
        </View>
36
    )
37
}
38

  
39
export default SearchFormControl
src/components/search/SwitchWithLabel.tsx
1
import { Switch, View, Text } from "native-base"
2

  
3
interface SwitchWithLabelProps {
4
    label: string,
5
    value: boolean,
6
    onValueChange: (value: boolean) => void,
7
}
8

  
9
const SwitchWithLabel = (props: SwitchWithLabelProps) => {
10

  
11
    return (
12
        <View
13
            flexDirection={ "row" }
14
            alignItems={ "center" }
15
            justifyContent={ "space-between" }
16
        >
17
            <Text>{ props.label }</Text>
18
            <Switch value={ props.value } onValueChange={ props.onValueChange } size={"sm"} key={props.label}/>
19
        </View>
20
    )
21
}
22

  
23
export default SwitchWithLabel
src/logging/logger.ts
1
import { logger } from 'react-native-logs'
2

  
3
const config = {
4
    severity: 'debug',
5
    transportOptions: {
6
        colors: false,
7
        printLevel: true,
8
        printDate: true,
9
        enabled: true,
10
        tag: 'custom-tag',
11
        format: (msg: string) => msg,
12
        dateFormat: 'hh:mm:ss',
13
    },
14
    levels: {
15
        debug: 0,
16
        info: 1,
17
        warn: 2,
18
        error: 3,
19
    },
20
    async: false,
21
}
22

  
23
export const log = logger.createLogger(config)
src/pages/SearchPage.tsx
1
import { Center, KeyboardAvoidingView, ScrollView, Text, VStack } from "native-base"
1
import {
2
    Button,
3
    Center,
4
    ChevronDownIcon,
5
    ChevronUpIcon, ScrollView,
6
    VStack
7
} from "native-base"
2 8
import ListView from "../components/listView/ListView"
3 9
import SearchForm from "../components/search/SearchForm"
4
import { Platform } from "react-native"
10
import { useEffect, useState } from "react"
11
import {
12
    fetchArtists, fetchCities, fetchCountries, fetchInstitutions,
13
    fetchInventories,
14
    fetchNationalities,
15
    fetchPlans,
16
    fetchSubjects, fetchTechniques
17
} from "../stores/actions/searchFormThunks"
18
import { useDispatch } from "react-redux"
19
import { AppDispatch } from "../stores/store"
20
import { log } from "../logging/logger"
5 21

  
6 22
const SearchPage = () => {
23
    const [isFilterOpen, setIsFilterOpen] = useState(true)
24

  
25
    const dispatch = useDispatch<AppDispatch>()
26

  
27
    useEffect(() => {
28
        log.debug("SearchPage", "useEffect", "fetchEverything")
29

  
30
        dispatch(fetchInventories())
31
        dispatch(fetchNationalities())
32
        dispatch(fetchArtists())
33
        dispatch(fetchSubjects())
34
        dispatch(fetchPlans())
35
        dispatch(fetchTechniques())
36
        dispatch(fetchCountries())
37
        dispatch(fetchCities())
38
        dispatch(fetchInstitutions())
39

  
40
        log.debug("SearchPage", "useEffect", "fetchEverything", "done")
41
    }, [dispatch])
7 42

  
8 43
    return (
9
        <KeyboardAvoidingView
10
            h={ {
11
                lg: "auto"
12
            } }
13
            behavior={ Platform.OS === "ios" ? "padding" : "height" }
14
        >
15
            <ScrollView>
16
                <VStack>
17
                    <SearchForm/>
44
        <Center m={ 2 } mr={ 4 } flex={1}>
45
            <Button
46
                alignSelf={ "start" }
47
                onPress={ () => setIsFilterOpen(!isFilterOpen) }
48
                variant="outline"
49
                endIcon={
50
                    <>
51
                        { isFilterOpen ?
52
                            (<ChevronUpIcon size={ 4 } color={ "primary.500" }/>)
53
                            : (<ChevronDownIcon size={ 4 } color={ "primary.500" }/>) }
54
                    </>
55
                }
56
                flexWrap={ "nowrap" }
57
                borderColor={ "primary.500" }
58
                mb={ 2 }
59
            >
60
                Filter
61
            </Button>
62
            <ScrollView flex={1} w={"100%"} >
63
                <VStack space={ 1 }>
64
                    <SearchForm isFilterOpen={ isFilterOpen }/>
18 65
                    <ListView/>
19 66
                </VStack>
20 67
            </ScrollView>
21
        </KeyboardAvoidingView>
68
        </Center>
22 69
    )
23 70
}
24 71

  
src/stores/reducers/listViewSlice.ts
1 1
import { createSlice } from "@reduxjs/toolkit"
2 2
import { Inventory } from "../../types/searchFormTypes"
3
import { ItemPreview } from "../../types/listViewTypes"
3
import { ItemPreviewType } from "../../types/listViewTypes"
4 4
import { search } from "../actions/listViewThunks"
5 5

  
6 6
export interface ListViewState {
7 7
    inventories: Inventory[]
8
    data: ItemPreview[]
8
    data: ItemPreviewType[]
9 9
    loading: boolean
10 10
    lastError?: string
11 11
}
src/theme/nativeBaseTheme.ts
3 3
export const nativeBaseTheme = extendTheme({
4 4
    colors: {
5 5
        primary: {
6
            50: "#4F4537",
7
            100: "#E3A400",
6
            50: '#FFF8E1',
7
            100: '#FFE082',
8
            200: '#FFD54F',
9
            300: '#FFCA28',
10
            400: '#FFC107',
11
            500: '#E3A400',
12
            600: '#C88700',
13
            700: '#AD6F00',
14
            800: '#8B5800',
15
            900: '#5D3A00',
8 16
        },
9 17

  
10 18
    }
src/types/listViewTypes.ts
3 3

  
4 4
export type SearchResponse = {
5 5
    pagination: Pagination
6
    data: ItemPreview[]
6
    data: ItemPreviewType[]
7 7
}
8 8

  
9 9
export type Pagination = {
......
13 13
    inventories: Inventory[]
14 14
}
15 15

  
16
export type ItemPreview = {
16
export type ItemPreviewType = {
17 17
    xml_id: string
18 18
    iconclass_external_id: string
19 19
    text: string
......
33 33
        images?: ItemImage[]
34 34
        origDate?: string
35 35
    }
36
]
36
] | {
37
    images?: ItemImage[]
38
    origDate?: string
39
}[]
40

  
37 41

  
38 42
export type PersonName = {
39 43
    value?: string

Také k dispozici: Unified diff