Revize af8eb9a6
Přidáno uživatelem Schwobik před téměř 2 roky(ů)
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
Implementation of multiselect component, search page finished with listview
re #10478