improvement:frontend search filter on organizations
Signed-off-by: manish-singh-bisht <mthefool218@gmail.com>
This commit is contained in:
committed by
Sacha Al Himdani
parent
001a2aa4ac
commit
bb4876fd4f
@@ -16,18 +16,20 @@ import {
|
|||||||
IconPlusLarge,
|
IconPlusLarge,
|
||||||
IconCheckmark1,
|
IconCheckmark1,
|
||||||
IconClock,
|
IconClock,
|
||||||
|
IconMagnifyingGlass,
|
||||||
useToast,
|
useToast,
|
||||||
Logo,
|
Logo,
|
||||||
Toasts,
|
Toasts,
|
||||||
ConfirmDialog,
|
ConfirmDialog,
|
||||||
Avatar,
|
Avatar,
|
||||||
Badge,
|
Badge,
|
||||||
|
Input,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
import { useLazyLoadQuery } from "react-relay";
|
import { useLazyLoadQuery } from "react-relay";
|
||||||
import type { EmployeeLayoutQuery as EmployeeLayoutQueryType } from "./__generated__/EmployeeLayoutQuery.graphql";
|
import type { EmployeeLayoutQuery as EmployeeLayoutQueryType } from "./__generated__/EmployeeLayoutQuery.graphql";
|
||||||
import { Suspense, useState, useEffect, use } from "react";
|
import { Suspense, useState, useEffect, useMemo, use } from "react";
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
import { PageError } from "/components/PageError";
|
import { PageError } from "/components/PageError";
|
||||||
import { buildEndpoint } from "/providers/RelayProviders";
|
import { buildEndpoint } from "/providers/RelayProviders";
|
||||||
@@ -152,8 +154,18 @@ function OrganizationSelector({
|
|||||||
const [pendingInvitationsCount, setPendingInvitationsCount] = useState(0);
|
const [pendingInvitationsCount, setPendingInvitationsCount] = useState(0);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
|
const filteredOrganizations = useMemo(() => {
|
||||||
|
if (!search.trim()) {
|
||||||
|
return organizations;
|
||||||
|
}
|
||||||
|
return organizations.filter((org) =>
|
||||||
|
org.name.toLowerCase().includes(search.toLowerCase())
|
||||||
|
);
|
||||||
|
}, [organizations, search]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -218,17 +230,31 @@ function OrganizationSelector({
|
|||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{!isLoading && organizations.length > 0 && (
|
||||||
|
<div className="px-3 py-2 border-b border-border-low">
|
||||||
|
<Input
|
||||||
|
icon={IconMagnifyingGlass}
|
||||||
|
placeholder={__("Search organizations...")}
|
||||||
|
value={search}
|
||||||
|
onValueChange={setSearch}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="max-h-150 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-transparent hover:scrollbar-thumb-gray-400">
|
<div className="max-h-150 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-transparent hover:scrollbar-thumb-gray-400">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="px-3 py-2 text-gray-500">
|
<div className="px-3 py-2 text-gray-500">
|
||||||
{__("Loading organizations...")}
|
{__("Loading organizations...")}
|
||||||
</div>
|
</div>
|
||||||
) : organizations.length === 0 ? (
|
) : filteredOrganizations.length === 0 ? (
|
||||||
<div className="px-3 py-2 text-gray-500">
|
<div className="px-3 py-2 text-gray-500">
|
||||||
{__("No organizations found")}
|
{__("No organizations found")}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
organizations.map((organization) => {
|
filteredOrganizations.map((organization) => {
|
||||||
const isAuthenticated =
|
const isAuthenticated =
|
||||||
organization.authStatus === "authenticated";
|
organization.authStatus === "authenticated";
|
||||||
const isExpired = organization.authStatus === "expired";
|
const isExpired = organization.authStatus === "expired";
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
IconKey,
|
IconKey,
|
||||||
IconListStack,
|
IconListStack,
|
||||||
IconLock,
|
IconLock,
|
||||||
|
IconMagnifyingGlass,
|
||||||
IconMedal,
|
IconMedal,
|
||||||
IconPageTextLine,
|
IconPageTextLine,
|
||||||
IconPeopleAdd,
|
IconPeopleAdd,
|
||||||
@@ -32,6 +33,7 @@ import {
|
|||||||
IconShield,
|
IconShield,
|
||||||
IconStore,
|
IconStore,
|
||||||
IconTodo,
|
IconTodo,
|
||||||
|
Input,
|
||||||
Layout,
|
Layout,
|
||||||
SidebarItem,
|
SidebarItem,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
@@ -39,7 +41,7 @@ import {
|
|||||||
UserDropdown as UserDropdownRoot,
|
UserDropdown as UserDropdownRoot,
|
||||||
useToast,
|
useToast,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { Suspense, use, useEffect, useState } from "react";
|
import { Suspense, use, useEffect, useMemo, useState } from "react";
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
import { useLazyLoadQuery } from "react-relay";
|
import { useLazyLoadQuery } from "react-relay";
|
||||||
import { Link, Navigate, Outlet, useParams } from "react-router";
|
import { Link, Navigate, Outlet, useParams } from "react-router";
|
||||||
@@ -364,8 +366,18 @@ function OrganizationSelector({
|
|||||||
const [pendingInvitationsCount, setPendingInvitationsCount] = useState(0);
|
const [pendingInvitationsCount, setPendingInvitationsCount] = useState(0);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
|
const filteredOrganizations = useMemo(() => {
|
||||||
|
if (!search.trim()) {
|
||||||
|
return organizations;
|
||||||
|
}
|
||||||
|
return organizations.filter((org) =>
|
||||||
|
org.name.toLowerCase().includes(search.toLowerCase())
|
||||||
|
);
|
||||||
|
}, [organizations, search]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -430,17 +442,29 @@ function OrganizationSelector({
|
|||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<div className="px-3 py-2">
|
||||||
|
<Input
|
||||||
|
icon={IconMagnifyingGlass}
|
||||||
|
placeholder={__("Search organizations...")}
|
||||||
|
value={search}
|
||||||
|
onValueChange={setSearch}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="max-h-150 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-transparent hover:scrollbar-thumb-gray-400">
|
<div className="max-h-150 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-transparent hover:scrollbar-thumb-gray-400">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="px-3 py-2 text-gray-500">
|
<div className="px-3 py-2 text-gray-500">
|
||||||
{__("Loading organizations...")}
|
{__("Loading organizations...")}
|
||||||
</div>
|
</div>
|
||||||
) : organizations.length === 0 ? (
|
) : filteredOrganizations.length === 0 ? (
|
||||||
<div className="px-3 py-2 text-gray-500">
|
<div className="px-3 py-2 text-gray-500">
|
||||||
{__("No organizations found")}
|
{__("No organizations found")}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
organizations.map((organization) => {
|
filteredOrganizations.map((organization) => {
|
||||||
const isAuthenticated =
|
const isAuthenticated =
|
||||||
organization.authStatus === "authenticated";
|
organization.authStatus === "authenticated";
|
||||||
const isExpired = organization.authStatus === "expired";
|
const isExpired = organization.authStatus === "expired";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { Link, useNavigate } from "react-router";
|
import { Link, useNavigate } from "react-router";
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
@@ -9,7 +9,9 @@ import {
|
|||||||
IconCheckmark1,
|
IconCheckmark1,
|
||||||
IconLock,
|
IconLock,
|
||||||
IconClock,
|
IconClock,
|
||||||
|
IconMagnifyingGlass,
|
||||||
Badge,
|
Badge,
|
||||||
|
Input,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { usePageTitle } from "@probo/hooks";
|
import { usePageTitle } from "@probo/hooks";
|
||||||
import { formatDate } from "@probo/helpers";
|
import { formatDate } from "@probo/helpers";
|
||||||
@@ -46,6 +48,16 @@ export default function OrganizationsPage() {
|
|||||||
const [invitations, setInvitations] = useState<Invitation[]>([]);
|
const [invitations, setInvitations] = useState<Invitation[]>([]);
|
||||||
const [isLoadingInvitations, setIsLoadingInvitations] = useState(true);
|
const [isLoadingInvitations, setIsLoadingInvitations] = useState(true);
|
||||||
const [isAccepting, setIsAccepting] = useState(false);
|
const [isAccepting, setIsAccepting] = useState(false);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
const filteredOrganizations = useMemo(() => {
|
||||||
|
if (!search.trim()) {
|
||||||
|
return organizations;
|
||||||
|
}
|
||||||
|
return organizations.filter((org) =>
|
||||||
|
org.name.toLowerCase().includes(search.toLowerCase())
|
||||||
|
);
|
||||||
|
}, [organizations, search]);
|
||||||
|
|
||||||
// Fetch organizations from REST endpoint
|
// Fetch organizations from REST endpoint
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -163,12 +175,28 @@ export default function OrganizationsPage() {
|
|||||||
{__("Your organizations")}
|
{__("Your organizations")}
|
||||||
</h2>
|
</h2>
|
||||||
)}
|
)}
|
||||||
{organizations.map((organization) => (
|
{organizations.length > 3 && (
|
||||||
<OrganizationCard
|
<div className="w-full">
|
||||||
key={organization.id}
|
<Input
|
||||||
organization={organization}
|
icon={IconMagnifyingGlass}
|
||||||
/>
|
placeholder={__("Search organizations...")}
|
||||||
))}
|
value={search}
|
||||||
|
onValueChange={setSearch}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{filteredOrganizations.length === 0 ? (
|
||||||
|
<div className="text-center text-txt-secondary py-4">
|
||||||
|
{__("No organizations found")}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
filteredOrganizations.map((organization) => (
|
||||||
|
<OrganizationCard
|
||||||
|
key={organization.id}
|
||||||
|
organization={organization}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Card padded>
|
<Card padded>
|
||||||
|
|||||||
Reference in New Issue
Block a user