This repository has no description
semble
/
src
/
webapp
/
features
/
collections
/
components
/
collectionSelector
/
CollectionSelector.tsx
7.6 kB
232 lines
1'use client';
2
3import {
4 ScrollArea,
5 Stack,
6 TextInput,
7 Text,
8 Alert,
9 Loader,
10 CloseButton,
11 Button,
12 Group,
13 Divider,
14} from '@mantine/core';
15import { Fragment, useState } from 'react';
16import { useDebouncedValue } from '@mantine/hooks';
17import useMyCollections from '../../lib/queries/useMyCollections';
18import useCollectionSearch from '../../lib/queries/useCollectionSearch';
19import CollectionSelectorItemList from '../collectionSelectorItemList/CollectionSelectorItemList';
20import CreateCollectionDrawer from '@/features/collections/components/createCollectionDrawer/CreateCollectionDrawer';
21import CollectionSelectorError from './Error.CollectionSelector';
22import { FiPlus } from 'react-icons/fi';
23import { IoSearch } from 'react-icons/io5';
24
25interface Props {
26 isOpen: boolean;
27 onClose: () => void;
28 onCancel: () => void;
29 onSave: (e: React.FormEvent) => void;
30 isSaving?: boolean;
31 selectedCollections: SelectableCollectionItem[];
32 onSelectedCollectionsChange: (
33 collectionIds: SelectableCollectionItem[],
34 ) => void;
35}
36
37export default function CollectionSelector(props: Props) {
38 const { data, error } = useMyCollections();
39 const [search, setSearch] = useState<string>('');
40 const [debouncedSearch] = useDebouncedValue(search, 200);
41 const searchedCollections = useCollectionSearch({ query: debouncedSearch });
42 const [isDrawerOpen, setIsDrawerOpen] = useState(false);
43
44 const handleCollectionChange = (
45 checked: boolean,
46 item: SelectableCollectionItem,
47 ) => {
48 if (checked) {
49 if (!props.selectedCollections.some((col) => col.id === item.id)) {
50 props.onSelectedCollectionsChange([...props.selectedCollections, item]);
51 }
52 } else {
53 props.onSelectedCollectionsChange(
54 props.selectedCollections.filter((col) => col.id !== item.id),
55 );
56 }
57 };
58
59 if (error) {
60 return <CollectionSelectorError />;
61 }
62
63 const allCollections =
64 data?.pages.flatMap((page) => page.collections ?? []) ?? [];
65
66 const hasCollections = allCollections.length > 0;
67 const hasSelectedCollections = props.selectedCollections.length > 0;
68
69 // filter out selected from all to avoid duplication
70 const unselectedCollections = allCollections.filter(
71 (c) => !props.selectedCollections.some((sel) => sel.id === c.id),
72 );
73
74 return (
75 <Fragment>
76 <Stack gap="xl">
77 <Stack>
78 <TextInput
79 placeholder="Search for collections"
80 value={search}
81 onChange={(e) => setSearch(e.currentTarget.value)}
82 size="md"
83 variant="filled"
84 id="search"
85 leftSection={<IoSearch size={22} />}
86 rightSection={
87 <CloseButton
88 aria-label="Clear input"
89 onClick={() => setSearch('')}
90 style={{ display: search ? undefined : 'none' }}
91 />
92 }
93 />
94
95 <ScrollArea.Autosize mah={340} type="auto">
96 <Stack gap="xs">
97 {search ? (
98 <>
99 <Button
100 variant="light"
101 size="md"
102 color="grape"
103 radius="lg"
104 leftSection={<FiPlus size={22} />}
105 onClick={() => setIsDrawerOpen(true)}
106 >
107 Create new collection "{search}"
108 </Button>
109
110 {searchedCollections.isPending && (
111 <Stack align="center">
112 <Text fw={500} c="gray">
113 Searching collections...
114 </Text>
115 <Loader color="gray" />
116 </Stack>
117 )}
118
119 {searchedCollections.data &&
120 (searchedCollections.data.collections.length === 0 ? (
121 <Alert
122 color="gray"
123 title={`No results found for "${search}"`}
124 />
125 ) : (
126 <CollectionSelectorItemList
127 collections={searchedCollections.data.collections}
128 selectedCollections={props.selectedCollections}
129 onChange={handleCollectionChange}
130 />
131 ))}
132 </>
133 ) : hasCollections ? (
134 <>
135 <Button
136 variant="light"
137 size="md"
138 color="grape"
139 radius="lg"
140 leftSection={<FiPlus size={22} />}
141 onClick={() => setIsDrawerOpen(true)}
142 >
143 Create new collection
144 </Button>
145
146 {/* selected collections */}
147 {hasSelectedCollections && (
148 <Fragment>
149 <Text fw={600} fz={'sm'} c={'gray'}>
150 Selected Collections ({props.selectedCollections.length}
151 )
152 </Text>
153 <CollectionSelectorItemList
154 collections={props.selectedCollections}
155 selectedCollections={props.selectedCollections}
156 onChange={handleCollectionChange}
157 />
158 {unselectedCollections.length > 0 && <Divider my="xs" />}
159 </Fragment>
160 )}
161
162 {/* remaining collections */}
163 {unselectedCollections.length > 0 ? (
164 <CollectionSelectorItemList
165 collections={unselectedCollections}
166 selectedCollections={props.selectedCollections}
167 onChange={handleCollectionChange}
168 />
169 ) : (
170 !hasSelectedCollections && (
171 <Alert color="gray" title="No collections available" />
172 )
173 )}
174 </>
175 ) : (
176 <Stack align="center" gap="xs">
177 <Text fz="lg" fw={600} c="gray">
178 No collections
179 </Text>
180 <Button
181 onClick={() => setIsDrawerOpen(true)}
182 variant="light"
183 color="gray"
184 rightSection={<FiPlus size={22} />}
185 >
186 Create a collection
187 </Button>
188 </Stack>
189 )}
190 </Stack>
191 </ScrollArea.Autosize>
192 </Stack>
193
194 {/* Action Buttons */}
195 <Group justify="space-between" gap="xs" grow>
196 <Button
197 variant="light"
198 color="gray"
199 size="md"
200 onClick={() => props.onCancel()}
201 >
202 Cancel
203 </Button>
204
205 <Button
206 size="md"
207 loading={props.isSaving}
208 onClick={(e) => {
209 props.onSave(e);
210 }}
211 >
212 Save
213 </Button>
214 </Group>
215 </Stack>
216
217 <CreateCollectionDrawer
218 key={search}
219 isOpen={isDrawerOpen}
220 onClose={() => setIsDrawerOpen(false)}
221 initialName={search}
222 onCreate={(newCollection) => {
223 props.onSelectedCollectionsChange([
224 ...props.selectedCollections,
225 newCollection,
226 ]);
227 setSearch('');
228 }}
229 />
230 </Fragment>
231 );
232}