improvement:frontend search filter on organizations

Signed-off-by: manish-singh-bisht <mthefool218@gmail.com>
This commit is contained in:
manish-singh-bisht
2025-12-13 21:36:24 +05:30
committed by Sacha Al Himdani
parent 001a2aa4ac
commit bb4876fd4f
3 changed files with 92 additions and 14 deletions

View File

@@ -16,18 +16,20 @@ import {
IconPlusLarge,
IconCheckmark1,
IconClock,
IconMagnifyingGlass,
useToast,
Logo,
Toasts,
ConfirmDialog,
Avatar,
Badge,
Input,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { graphql } from "relay-runtime";
import { useLazyLoadQuery } from "react-relay";
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 { PageError } from "/components/PageError";
import { buildEndpoint } from "/providers/RelayProviders";
@@ -152,8 +154,18 @@ function OrganizationSelector({
const [pendingInvitationsCount, setPendingInvitationsCount] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [search, setSearch] = useState("");
const { __ } = useTranslate();
const filteredOrganizations = useMemo(() => {
if (!search.trim()) {
return organizations;
}
return organizations.filter((org) =>
org.name.toLowerCase().includes(search.toLowerCase())
);
}, [organizations, search]);
useEffect(() => {
const fetchData = async () => {
try {
@@ -218,17 +230,31 @@ function OrganizationSelector({
</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">
{isLoading ? (
<div className="px-3 py-2 text-gray-500">
{__("Loading organizations...")}
</div>
) : organizations.length === 0 ? (
) : filteredOrganizations.length === 0 ? (
<div className="px-3 py-2 text-gray-500">
{__("No organizations found")}
</div>
) : (
organizations.map((organization) => {
filteredOrganizations.map((organization) => {
const isAuthenticated =
organization.authStatus === "authenticated";
const isExpired = organization.authStatus === "expired";

View File

@@ -23,6 +23,7 @@ import {
IconKey,
IconListStack,
IconLock,
IconMagnifyingGlass,
IconMedal,
IconPageTextLine,
IconPeopleAdd,
@@ -32,6 +33,7 @@ import {
IconShield,
IconStore,
IconTodo,
Input,
Layout,
SidebarItem,
Skeleton,
@@ -39,7 +41,7 @@ import {
UserDropdown as UserDropdownRoot,
useToast,
} 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 { useLazyLoadQuery } from "react-relay";
import { Link, Navigate, Outlet, useParams } from "react-router";
@@ -364,8 +366,18 @@ function OrganizationSelector({
const [pendingInvitationsCount, setPendingInvitationsCount] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [search, setSearch] = useState("");
const { __ } = useTranslate();
const filteredOrganizations = useMemo(() => {
if (!search.trim()) {
return organizations;
}
return organizations.filter((org) =>
org.name.toLowerCase().includes(search.toLowerCase())
);
}, [organizations, search]);
useEffect(() => {
const fetchData = async () => {
try {
@@ -429,18 +441,30 @@ function OrganizationSelector({
{isLoading ? __("Loading...") : currentOrganization?.name || ""}
</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">
{isLoading ? (
<div className="px-3 py-2 text-gray-500">
{__("Loading organizations...")}
</div>
) : organizations.length === 0 ? (
) : filteredOrganizations.length === 0 ? (
<div className="px-3 py-2 text-gray-500">
{__("No organizations found")}
</div>
) : (
organizations.map((organization) => {
filteredOrganizations.map((organization) => {
const isAuthenticated =
organization.authStatus === "authenticated";
const isExpired = organization.authStatus === "expired";

View File

@@ -1,5 +1,5 @@
import { useTranslate } from "@probo/i18n";
import { useEffect, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { Link, useNavigate } from "react-router";
import {
Avatar,
@@ -9,7 +9,9 @@ import {
IconCheckmark1,
IconLock,
IconClock,
IconMagnifyingGlass,
Badge,
Input,
} from "@probo/ui";
import { usePageTitle } from "@probo/hooks";
import { formatDate } from "@probo/helpers";
@@ -46,6 +48,16 @@ export default function OrganizationsPage() {
const [invitations, setInvitations] = useState<Invitation[]>([]);
const [isLoadingInvitations, setIsLoadingInvitations] = useState(true);
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
useEffect(() => {
@@ -163,12 +175,28 @@ export default function OrganizationsPage() {
{__("Your organizations")}
</h2>
)}
{organizations.map((organization) => (
<OrganizationCard
key={organization.id}
organization={organization}
/>
))}
{organizations.length > 3 && (
<div className="w-full">
<Input
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>
)}
<Card padded>