feat: add improved search (#1051)

* feat: add improved search

* wip: add support for sorting, rename use-options to use-query-options, add use-options for local usage, add pages search group

* feat: add help links from manage layout to help search mode

* feat: add additional search engines

* feat: add group search details

* refactor: improve users search group type

* feat: add apps search group, add disabled search interaction

* feat: add integrations and boards for search

* wip: hook issue with react

* fix: hook issue regarding actions and interactions

* chore: address pull request feedback

* fix: format issues

* feat: add additional global actions to search

* chore: remove unused code

* fix: search engine short key

* fix: typecheck issues

* fix: deepsource issues

* fix: eslint issue

* fix: lint issues

* fix: unordered dependencies

* chore: address pull request feedback
This commit is contained in:
Meier Lukas
2024-09-20 16:51:42 +02:00
committed by GitHub
parent 0c44af2f67
commit ce1ef3cbe7
64 changed files with 1985 additions and 628 deletions

View File

@@ -0,0 +1,83 @@
import { Group, Stack, Text } from "@mantine/core";
import { IconEye, IconUsersGroup } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import { useI18n } from "@homarr/translation/client";
import { createChildrenOptions } from "../../lib/children";
import { createGroup } from "../../lib/group";
import { interaction } from "../../lib/interaction";
// This has to be type so it can be interpreted as Record<string, unknown>.
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
type Group = { id: string; name: string };
const groupChildrenOptions = createChildrenOptions<Group>({
useActions: () => [
{
key: "detail",
component: () => {
const t = useI18n();
return (
<Group mx="md" my="sm">
<IconEye stroke={1.5} />
<Text>{t("search.mode.userGroup.group.group.children.action.detail.label")}</Text>
</Group>
);
},
useInteraction: interaction.link(({ id }) => ({ href: `/manage/users/groups/${id}` })),
},
{
key: "manageMember",
component: () => {
const t = useI18n();
return (
<Group mx="md" my="sm">
<IconUsersGroup stroke={1.5} />
<Text>{t("search.mode.userGroup.group.group.children.action.manageMember.label")}</Text>
</Group>
);
},
useInteraction: interaction.link(({ id }) => ({ href: `/manage/users/groups/${id}/members` })),
},
{
key: "managePermission",
component: () => {
const t = useI18n();
return (
<Group mx="md" my="sm">
<IconEye stroke={1.5} />
<Text>{t("search.mode.userGroup.group.group.children.action.managePermission.label")}</Text>
</Group>
);
},
useInteraction: interaction.link(({ id }) => ({ href: `/manage/users/groups/${id}/permissions` })),
},
],
detailComponent: ({ options }) => {
const t = useI18n();
return (
<Stack mx="md" my="sm">
<Text>{t("search.mode.userGroup.group.group.children.detail.title")}</Text>
<Group>
<Text>{options.name}</Text>
</Group>
</Stack>
);
},
});
export const groupsSearchGroup = createGroup<Group>({
keyPath: "id",
title: "Groups",
component: ({ name }) => (
<Group px="md" py="sm">
<Text>{name}</Text>
</Group>
),
useInteraction: interaction.children(groupChildrenOptions),
useQueryOptions(query) {
return clientApi.group.search.useQuery({ query, limit: 5 });
},
});

View File

@@ -0,0 +1,9 @@
import type { SearchMode } from "../../lib/mode";
import { groupsSearchGroup } from "./groups-search-group";
import { usersSearchGroup } from "./users-search-group";
export const userGroupMode = {
modeKey: "userGroup",
character: "@",
groups: [usersSearchGroup, groupsSearchGroup],
} satisfies SearchMode;

View File

@@ -0,0 +1,62 @@
import { Group, Stack, Text } from "@mantine/core";
import { IconEye } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import { useI18n } from "@homarr/translation/client";
import { UserAvatar } from "@homarr/ui";
import { createChildrenOptions } from "../../lib/children";
import { createGroup } from "../../lib/group";
import { interaction } from "../../lib/interaction";
// This has to be type so it can be interpreted as Record<string, unknown>.
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
type User = { id: string; name: string; image: string | null };
const userChildrenOptions = createChildrenOptions<User>({
useActions: () => [
{
key: "detail",
component: () => {
const t = useI18n();
return (
<Group mx="md" my="sm">
<IconEye stroke={1.5} />
<Text>{t("search.mode.userGroup.group.user.children.action.detail.label")}</Text>
</Group>
);
},
useInteraction: interaction.link(({ id }) => ({ href: `/manage/users/${id}/general` })),
},
],
detailComponent: ({ options }) => {
const t = useI18n();
return (
<Stack mx="md" my="sm">
<Text>{t("search.mode.userGroup.group.user.children.detail.title")}</Text>
<Group>
<UserAvatar user={options} size="sm" />
<Text>{options.name}</Text>
</Group>
</Stack>
);
},
});
export const usersSearchGroup = createGroup<User>({
keyPath: "id",
title: (t) => t("search.mode.userGroup.group.user.title"),
component: (user) => (
<Group px="md" py="sm">
<UserAvatar user={user} size="sm" />
<Text>{user.name}</Text>
</Group>
),
useInteraction: interaction.children(userChildrenOptions),
useQueryOptions(query) {
return clientApi.user.search.useQuery({ query, limit: 5 });
},
});