325 lines
9.2 KiB
TypeScript
325 lines
9.2 KiB
TypeScript
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
|
import { Box, Button, Card, Group, Stack, Text, TextInput } from '@mantine/core';
|
|
import { isNotEmpty, useForm } from '@mantine/form';
|
|
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
|
import { AddItemModal, NoItemsFound, SettingsHeader } from 'components';
|
|
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
|
import { useEffect, useState } from 'react';
|
|
import { textInputClasses } from 'styles';
|
|
import type { Bookmark, BookmarkGroup } from 'types';
|
|
import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem';
|
|
|
|
const BookmarkSettings = () => {
|
|
const defaultGroup = {
|
|
id: null,
|
|
categoryName: '',
|
|
categoryIndex: 0,
|
|
filterName: '',
|
|
};
|
|
const form = useForm({
|
|
initialValues: defaultGroup,
|
|
validate: {
|
|
categoryName: isNotEmpty('Category name is required'),
|
|
filterName: isNotEmpty('Filter name is required'),
|
|
},
|
|
});
|
|
const bookmarksEndpoint = 'api/bookmarks';
|
|
const { data: bookmarkData, isFetched: bookmarksIsFetched } = useQueryAPI({
|
|
endpoint: bookmarksEndpoint,
|
|
});
|
|
const [bookmarks, setBookmarks] = useState<Bookmark[]>([]);
|
|
const categoriesEndpoint = 'api/bookmarkCategories';
|
|
const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({
|
|
endpoint: categoriesEndpoint,
|
|
});
|
|
const [dragging, setDragging] = useState(false);
|
|
const [groups, setGroups] = useState<BookmarkGroup[]>([]);
|
|
const [groupList, setGroupList] = useState<BookmarkGroup[]>([]);
|
|
const { isMobile, reorder, theme } = useCommonHooks();
|
|
const { mutateAsync } = useMutationAPI();
|
|
const [searchValue, setSearchValue] = useState('');
|
|
const [debouncedGroupSearch] = useDebouncedValue(searchValue, 500);
|
|
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const handleClose = () => {
|
|
closeModal();
|
|
form.reset();
|
|
};
|
|
|
|
function onDragEnd(result: any) {
|
|
if (!result.destination) {
|
|
return;
|
|
}
|
|
|
|
if (result.destination.index === result.source.index) {
|
|
return;
|
|
}
|
|
|
|
const items = reorder(groups, result.source.index, result.destination.index);
|
|
setGroupList(
|
|
items.map((item: any, index: number) => {
|
|
return {
|
|
...item,
|
|
index: index,
|
|
};
|
|
}),
|
|
);
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (bookmarkData && bookmarksIsFetched) {
|
|
setBookmarks(bookmarkData);
|
|
}
|
|
}, [bookmarksIsFetched, bookmarkData]);
|
|
|
|
useEffect(() => {
|
|
if (groupData && groupsIsFetched) {
|
|
const foundBookmarkGroups = [
|
|
...new Set(
|
|
bookmarks
|
|
.filter(
|
|
(a: Bookmark) =>
|
|
a.name.toLowerCase().includes(debouncedGroupSearch.toLowerCase()) ||
|
|
a.url.toLowerCase().includes(debouncedGroupSearch.toLowerCase()),
|
|
)
|
|
.map(
|
|
(a: Bookmark) =>
|
|
groupData.filter(
|
|
(b: BookmarkGroup) =>
|
|
b.categoryName.toLowerCase() === a.category.toLowerCase(),
|
|
)[0],
|
|
),
|
|
),
|
|
];
|
|
const foundGroups = groupData.filter((a: BookmarkGroup) =>
|
|
a.categoryName.toLowerCase().includes(debouncedGroupSearch.toLowerCase()),
|
|
);
|
|
|
|
const groupList = [...new Set([...foundBookmarkGroups, ...foundGroups])].sort(
|
|
(a: BookmarkGroup, b: BookmarkGroup) =>
|
|
a.categoryIndex > b.categoryIndex ? 1 : -1,
|
|
);
|
|
setGroups(groupList);
|
|
}
|
|
if (debouncedGroupSearch === searchValue) {
|
|
setIsLoading(false);
|
|
}
|
|
}, [groupData, groupsIsFetched, debouncedGroupSearch]);
|
|
|
|
useEffect(() => {
|
|
if (groupList.length > 0 && groupList !== groups) {
|
|
mutateAsync({
|
|
method: 'PATCH',
|
|
endpoint: 'api/bookmarkCategoryOrder',
|
|
body: groupList,
|
|
});
|
|
}
|
|
}, [groupList]);
|
|
|
|
return (
|
|
<Stack>
|
|
<AddItemModal
|
|
modalOpened={modalOpened}
|
|
handleClose={handleClose}
|
|
title={'Add a new Bookmark Group'}
|
|
body={
|
|
<form
|
|
onSubmit={form.onSubmit((values) => {
|
|
mutateAsync({
|
|
method: 'POST',
|
|
endpoint: categoriesEndpoint,
|
|
body: {
|
|
...values,
|
|
categoryIndex: groups.length,
|
|
},
|
|
});
|
|
handleClose();
|
|
})}
|
|
>
|
|
<Stack gap={0} w={'100%'} align='center'>
|
|
<Group w={'100%'} justify='center'>
|
|
<TextInput
|
|
key={form.key('categoryName')}
|
|
{...form.getInputProps('categoryName')}
|
|
label={'Category Name'}
|
|
placeholder='eg. Shopping'
|
|
className={textInputClasses.settingsItem}
|
|
classNames={textInputClasses}
|
|
withAsterisk
|
|
styles={
|
|
theme.textShadowsEnabled
|
|
? {
|
|
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
|
}
|
|
: undefined
|
|
}
|
|
/>
|
|
<TextInput
|
|
key={form.key('filterName')}
|
|
{...form.getInputProps('filterName')}
|
|
label={'Filter Name'}
|
|
placeholder='eg. Personal'
|
|
className={textInputClasses.settingsItem}
|
|
classNames={textInputClasses}
|
|
withAsterisk
|
|
styles={
|
|
theme.textShadowsEnabled
|
|
? {
|
|
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
|
}
|
|
: undefined
|
|
}
|
|
/>
|
|
</Group>
|
|
<Group mt={'1rem'}>
|
|
<Button type='submit' color={'green'}>
|
|
Save
|
|
</Button>
|
|
<Button
|
|
color={'red'}
|
|
styles={{ label: { color: 'black' } }}
|
|
onClick={handleClose}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
</Group>
|
|
</Stack>
|
|
</form>
|
|
}
|
|
/>
|
|
<SettingsHeader
|
|
setDragging={groups.length > 0 ? setDragging : undefined}
|
|
openModal={openModal}
|
|
title={'Bookmarks'}
|
|
tooltip={{
|
|
title: 'Bookmarks are the links that will be displayed on the homepage.',
|
|
highlightList: [
|
|
'Bookmark',
|
|
'Bookmarks',
|
|
'Group',
|
|
'Groups',
|
|
'Link',
|
|
'Links',
|
|
'Category',
|
|
'Categories',
|
|
],
|
|
description:
|
|
'Bookmarks can only belong to a single category, and will be displayed under that category on the homepage.',
|
|
}}
|
|
searchValue={searchValue}
|
|
setSearchValue={setSearchValue}
|
|
searchPlaceholder={'Search Bookmark Groups...'}
|
|
isLoading={isLoading}
|
|
setIsLoading={setIsLoading}
|
|
/>
|
|
{dragging ? (
|
|
<DragDropContext onDragEnd={onDragEnd}>
|
|
<Stack
|
|
mt={isMobile ? '10rem' : '6rem'}
|
|
mb={isMobile ? '6rem' : '1rem'}
|
|
align='center'
|
|
>
|
|
<Droppable droppableId={'groupList'}>
|
|
{(provided) => (
|
|
<Stack
|
|
w={'75%'}
|
|
align='center'
|
|
justify='center'
|
|
ref={provided.innerRef}
|
|
{...provided.droppableProps}
|
|
>
|
|
{groups?.map((group: BookmarkGroup) => (
|
|
<Draggable
|
|
key={group.id}
|
|
draggableId={`${group.id}`}
|
|
index={Number(group.categoryIndex)}
|
|
>
|
|
{(provided) => (
|
|
<Box
|
|
ref={provided.innerRef}
|
|
{...provided.draggableProps}
|
|
{...provided.dragHandleProps}
|
|
>
|
|
<Card
|
|
w={'400px'}
|
|
style={{
|
|
backgroundColor: 'var(--mantine-color-search-0)',
|
|
border: '1px solid var(--mantine-color-border-0)',
|
|
borderRadius: '8px',
|
|
}}
|
|
>
|
|
<Group gap={0} align='center' justify='space-between'>
|
|
<Stack gap={0}>
|
|
<Text c={'var(--mantine-color-accent-0)'} fz={'1rem'}>
|
|
Name
|
|
</Text>
|
|
<Text
|
|
p={'.25rem'}
|
|
style={{
|
|
borderRadius: '4px',
|
|
paddingLeft: '.5rem',
|
|
color: 'var(--mantine-color-primary-0)',
|
|
backgroundColor: 'var(--mantine-color-search-0)',
|
|
}}
|
|
>
|
|
{group.categoryName}
|
|
</Text>
|
|
</Stack>
|
|
<Stack gap={0}>
|
|
<Text c={'var(--mantine-color-accent-0)'} fz={'1rem'}>
|
|
Filter Name
|
|
</Text>
|
|
<Text
|
|
p={'.25rem'}
|
|
style={{
|
|
borderRadius: '4px',
|
|
paddingLeft: '.5rem',
|
|
color: 'var(--mantine-color-primary-0)',
|
|
backgroundColor: 'var(--mantine-color-search-0)',
|
|
}}
|
|
>
|
|
{group.filterName}
|
|
</Text>
|
|
</Stack>
|
|
</Group>
|
|
</Card>
|
|
</Box>
|
|
)}
|
|
</Draggable>
|
|
))}
|
|
{provided.placeholder}
|
|
</Stack>
|
|
)}
|
|
</Droppable>
|
|
</Stack>
|
|
</DragDropContext>
|
|
) : (
|
|
<Stack
|
|
mt={isMobile ? '10rem' : '6rem'}
|
|
mb={isMobile ? '6rem' : '1rem'}
|
|
align='center'
|
|
>
|
|
<Group w={isMobile ? undefined : '80%'} align='start' justify='center'>
|
|
{groups.length > 0 ? (
|
|
groups?.map((group: BookmarkGroup) => (
|
|
<DebouncedGroupSettingsItem
|
|
key={group.id}
|
|
bookmarkGroup={group}
|
|
bookmarks={bookmarks
|
|
.filter((a: Bookmark) => a.category === group.categoryName)
|
|
.sort((a: Bookmark, b: Bookmark) => (a.index > b.index ? 1 : -1))}
|
|
searchTerm={searchValue}
|
|
/>
|
|
))
|
|
) : (
|
|
<NoItemsFound />
|
|
)}
|
|
</Group>
|
|
</Stack>
|
|
)}
|
|
</Stack>
|
|
);
|
|
};
|
|
export default BookmarkSettings;
|