diff --git a/cypress/pageobject/Users/UserSearch.ts b/cypress/pageobject/Users/UserSearch.ts index 882a4dc8ea6..f12dc1c5361 100644 --- a/cypress/pageobject/Users/UserSearch.ts +++ b/cypress/pageobject/Users/UserSearch.ts @@ -83,6 +83,6 @@ export class UserPage { } verifyListView() { - cy.get("#user-list-view").should("have.class", "text-white"); + cy.get("#user-list-view").should("have.attr", "data-state", "active"); } } diff --git a/package-lock.json b/package-lock.json index fa8c7e1aa72..33b3a998beb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@radix-ui/react-popover": "^1.1.4", "@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-toast": "^1.2.4", "@radix-ui/react-tooltip": "^1.1.6", "@sentry/browser": "^8.47.0", @@ -4700,6 +4701,104 @@ } } }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.2.tgz", + "integrity": "sha512-9u/tQJMcC2aGq7KXpGivMm1mgq7oRJKXphDwdypPd/j21j/2znamPU8WkXgnhUaTrSFNIt8XhOyCAupg8/GbwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-roving-focus": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-presence": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-toast": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.4.tgz", diff --git a/package.json b/package.json index b768b4893aa..23c6a35423c 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "@radix-ui/react-popover": "^1.1.4", "@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-toast": "^1.2.4", "@radix-ui/react-tooltip": "^1.1.6", "@sentry/browser": "^8.47.0", diff --git a/src/components/Facility/FacilityUsers.tsx b/src/components/Facility/FacilityUsers.tsx index aeb9187a4cd..e61ffee53c6 100644 --- a/src/components/Facility/FacilityUsers.tsx +++ b/src/components/Facility/FacilityUsers.tsx @@ -2,8 +2,13 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import CountBlock from "@/CAREUI/display/Count"; +import CareIcon from "@/CAREUI/icons/CareIcon"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; + +import Loading from "@/components/Common/Loading"; import Page from "@/components/Common/Page"; +import SearchByMultipleFields from "@/components/Common/SearchByMultipleFields"; import UserListView from "@/components/Users/UserListAndCard"; import useFilters from "@/hooks/useFilters"; @@ -13,11 +18,14 @@ import useTanStackQueryInstead from "@/Utils/request/useQuery"; export default function FacilityUsers(props: { facilityId: number }) { const { t } = useTranslation(); + + let usersList: JSX.Element = <>; + const { qParams, updateQuery, Pagination, resultsPerPage } = useFilters({ limit: 18, cacheBlacklist: ["username"], }); - const [activeTab, setActiveTab] = useState(0); + const [activeTab, setActiveTab] = useState<"card" | "list">("card"); const { facilityId } = props; const { data: facilityData } = useTanStackQueryInstead( @@ -43,6 +51,20 @@ export default function FacilityUsers(props: { facilityId: number }) { prefetch: facilityId !== undefined, }); + if (userListLoading || !userListData) { + usersList = ; + } else { + usersList = ( +
+ + +
+ ); + } + return ( - - updateQuery({ username })} - searchValue={qParams.username} - activeTab={activeTab} - onTabChange={setActiveTab} - /> - - +
+
+ updateQuery({ username: value })} + options={[ + { + key: "username", + label: "username", + type: "text" as const, + placeholder: t("search_by_username"), + value: qParams.username || "", + shortcutKey: "u", + }, + ]} + /> +
+ setActiveTab(value as "card" | "list")} + > + + +
+ + Card +
+
+ +
+ + List +
+
+
+
+
+
{usersList}
); } diff --git a/src/components/Users/ManageUsers.tsx b/src/components/Users/ManageUsers.tsx index d28a0aeea52..edd08837199 100644 --- a/src/components/Users/ManageUsers.tsx +++ b/src/components/Users/ManageUsers.tsx @@ -6,9 +6,12 @@ import CountBlock from "@/CAREUI/display/Count"; import CareIcon from "@/CAREUI/icons/CareIcon"; import { AdvancedFilterButton } from "@/CAREUI/interactive/FiltersSlideover"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; + import ButtonV2 from "@/components/Common/ButtonV2"; import Loading from "@/components/Common/Loading"; import Page from "@/components/Common/Page"; +import SearchByMultipleFields from "@/components/Common/SearchByMultipleFields"; import UserFilter from "@/components/Users/UserFilter"; import UserListView from "@/components/Users/UserListAndCard"; @@ -29,17 +32,18 @@ export default function ManageUsers() { FilterBadges, advancedFilter, resultsPerPage, + clearSearch, } = useFilters({ limit: 18, cacheBlacklist: ["username"], }); - let manageUsers: JSX.Element = <>; + let usersList: JSX.Element = <>; const authUser = useAuthUser(); const userIndex = USER_TYPES.indexOf(authUser.user_type); const userTypes = authUser.is_superuser ? [...USER_TYPES] : USER_TYPES.slice(0, userIndex + 1); - const [activeTab, setActiveTab] = useState(0); + const [activeTab, setActiveTab] = useState<"card" | "list">("card"); const { data: homeFacilityData } = useTanStackQueryInstead( routes.getAnyFacility, @@ -94,23 +98,20 @@ export default function ManageUsers() { ); - if (userListLoading || districtDataLoading || !userListData?.results) { - return ; + if (userListLoading || districtDataLoading || !userListData) { + usersList = ; + } else { + usersList = ( +
+ + +
+ ); } - manageUsers = ( -
- updateQuery({ username })} - searchValue={qParams.username} - activeTab={activeTab} - onTabChange={setActiveTab} - /> - -
- ); - return (
@@ -170,7 +171,46 @@ export default function ManageUsers() {
-
{manageUsers}
+
+
+ updateQuery({ username: value })} + options={[ + { + key: "username", + label: "username", + type: "text" as const, + placeholder: t("search_by_username"), + value: qParams.username || "", + shortcutKey: "u", + }, + ]} + clearSearch={clearSearch} + /> +
+ setActiveTab(value as "card" | "list")} + > + + +
+ + Card +
+
+ +
+ + List +
+
+
+
+
+
{usersList}
); diff --git a/src/components/Users/UserListAndCard.tsx b/src/components/Users/UserListAndCard.tsx index d8194853570..e9acfbfd2b2 100644 --- a/src/components/Users/UserListAndCard.tsx +++ b/src/components/Users/UserListAndCard.tsx @@ -6,8 +6,6 @@ import Card from "@/CAREUI/display/Card"; import CareIcon from "@/CAREUI/icons/CareIcon"; import { Avatar } from "@/components/Common/Avatar"; -import Tabs from "@/components/Common/Tabs"; -import SearchInput from "@/components/Form/SearchInput"; import { UserAssignedModel, UserModel } from "@/components/Users/models"; import useAuthUser from "@/hooks/useAuthUser"; @@ -347,64 +345,15 @@ export const UserList = ({ }; interface UserListViewProps { users: UserModel[] | UserAssignedModel[]; - onSearch: (username: string) => void; - searchValue: string; - activeTab: number; - onTabChange: (tab: number) => void; + activeTab: string; } -export default function UserListView({ - users, - onSearch, - searchValue, - activeTab, - onTabChange, -}: UserListViewProps) { - const { t } = useTranslation(); - +export default function UserListView({ users, activeTab }: UserListViewProps) { return ( <> -
-
- onSearch(e.value)} - value={searchValue} - placeholder={t("search_by_username")} - /> -
- - - Card -
- ), - value: 0, - id: "user-card-view", - }, - { - text: ( -
- - List -
- ), - value: 1, - id: "user-list-view", - }, - ]} - currentTab={activeTab} - onTabChange={(tab) => onTabChange(tab as number)} - className="float-right" - /> - {users.length > 0 ? ( <> - {activeTab === 0 ? ( + {activeTab === "card" ? ( ) : ( diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx new file mode 100644 index 00000000000..d335de80adf --- /dev/null +++ b/src/components/ui/tabs.tsx @@ -0,0 +1,53 @@ +import * as TabsPrimitive from "@radix-ui/react-tabs"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Tabs = TabsPrimitive.Root; + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsList.displayName = TabsPrimitive.List.displayName; + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsContent.displayName = TabsPrimitive.Content.displayName; + +export { Tabs, TabsList, TabsTrigger, TabsContent };