Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4add241
feat: integrate Tanstack query for leaves management and add QueryCli…
rozita-hasani Mar 24, 2025
ac821cc
feat: implement leave creation using useCreateLeave mutation
rozita-hasani Mar 24, 2025
9a47a7f
feat: implement leave status update using useUpdateLeaveStatus mutation
rozita-hasani Mar 24, 2025
f9d14e2
feat: refactor leave policies handling to use custom hooks and improv…
rozita-hasani Mar 24, 2025
4158afa
feat: refactor leave policy handling to use custom hooks and improve …
rozita-hasani Mar 24, 2025
1def815
feat: refactor leave policy creation to use useCreateLeavesPolicy mut…
rozita-hasani Mar 24, 2025
aec3a5b
feat: refactor leave policy update to use useUpdateLeavePolicy mutati…
rozita-hasani Mar 24, 2025
76798f7
feat: refactor leave policy deletion to use useDeleteLeavePolicy muta…
rozita-hasani Mar 24, 2025
caf10b2
feat: refactor leave type methods to use custom hooks for data fetchi…
rozita-hasani Apr 15, 2025
6b307a8
feat: refactor HomePage and UserDetailsPage to use useLeaveBalance ho…
rozita-hasani Apr 16, 2025
41cdecf
feat: add useCreateLeavesCheck hook for leave creation validation
rozita-hasani Apr 16, 2025
e30d351
refactor: clean up leave type mutations by removing unnecessary setQu…
rozita-hasani Apr 16, 2025
5d4703b
refactor: replace team service calls with TanStack Query store hooks
rozita-hasani Apr 16, 2025
d84df37
refactor: replace organization service calls with TanStack Query stor…
rozita-hasani Apr 16, 2025
a7a53ef
refactor: replace holiday service calls with TanStack Query store hooks
rozita-hasani Apr 16, 2025
8d36611
refactor: replace auth service calls with TanStack Query store hooks
rozita-hasani Apr 16, 2025
8e5c54c
refactor: replace user service calls with TanStack Query store hooks
rozita-hasani Apr 17, 2025
9e60a61
refactor: replace notification service calls with TanStack Query stor…
rozita-hasani Apr 17, 2025
69f6616
refactor: update authentication and user-related hooks to use TanStac…
rozita-hasani Apr 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-tooltip": "^1.1.6",
"@tanstack/react-query": "^5.69.0",
"@tanstack/react-query-devtools": "^5.69.0",
"axios": "^1.7.7",
"caniuse-lite": "^1.0.30001639",
"chart.js": "^4.2.1",
Expand Down
38 changes: 38 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 21 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,28 @@ import React from "react";
import {UserContextProvider} from './contexts/UserContext';
import {ThemeContextProvider} from './contexts/ThemeContext';
import Root from "@/Routes.tsx";
import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
import {ReactQueryDevtools} from "@tanstack/react-query-devtools";

const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000,
gcTime: 10 * 60 * 1000,
refetchOnWindowFocus: false,
},
},
});

export default function App() {
return (
<ThemeContextProvider>
<UserContextProvider>
<Root/>
</UserContextProvider>
</ThemeContextProvider>
)
<QueryClientProvider client={queryClient}>
<ThemeContextProvider>
<UserContextProvider>
<Root />
</UserContextProvider>
</ThemeContextProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
49 changes: 13 additions & 36 deletions src/components/GlobalSearch.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,32 @@
import React, {useCallback, useContext, useEffect, useState} from 'react'
import React, {useContext, useState} from 'react'
import {useNavigate} from 'react-router-dom'
import {CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList,} from '@/components/ui/command'
import {Search, TreePalm} from 'lucide-react'
import {Button} from './ui/button'
import {getUsers} from '@/core/services/userService'
import {getLeavesPolicies} from '@/core/services/leaveService'
import {UserResponse} from '@/core/types/user'
import {LeavePolicyResponse} from '@/core/types/leave'
import UserAvatar from "@/modules/user/components/UserAvatar.tsx";
import {UserContext} from "@/contexts/UserContext.tsx";
import {getAccessibleNavigationItems} from "@/core/utils/navigation.ts";
import {UserRole} from "@/core/types/enum.ts";
import {useLeavesPolicies} from "@/core/stores/leavePoliciesStore.ts";
import {useUsers} from "@/core/stores/userStore.ts";

export function GlobalSearch() {
const [open, setOpen] = useState(false)
const [loading, setLoading] = useState(false)
const [users, setUsers] = useState<UserResponse[]>([])
const [policies, setPolicies] = useState<LeavePolicyResponse[]>([])
const [searchTerm, setSearchTerm] = useState('')
const navigate = useNavigate();
const {user} = useContext(UserContext);
const accessibleItems = user ? getAccessibleNavigationItems(user.role) : [];

const fetchData = useCallback(async () => {
try {
setLoading(true)
const [usersData, policiesData] = await Promise.all([
getUsers(0, 100),
getLeavesPolicies()
])
setUsers(usersData.contents)
setPolicies(policiesData)
} catch (error) {
console.error('Error fetching data:', error)
} finally {
setLoading(false)
}
}, [])

useEffect(() => {
if (open) {
fetchData()
}
}, [open, fetchData])
const {data : leavesPolicies} = useLeavesPolicies();
const { data: usersData, isPending } = useUsers(0, 100);
const users = usersData?.contents;

const onSelect = (path: string) => {
setOpen(false)
navigate(path)
}

const filteredUsers = users.filter(user => {
const filteredUsers = users?.filter(user => {
if (!searchTerm) return true;
const searchLower = searchTerm.toLowerCase();
return (
Expand All @@ -58,15 +35,15 @@ export function GlobalSearch() {
);
});

const filteredPolicies = policies.filter(policy => {
const filteredPolicies = leavesPolicies?.filter(policy => {
if (!searchTerm) return true;
const searchLower = searchTerm.toLowerCase();
return policy.name.toLowerCase().includes(searchLower);
});

const visibleUsers = searchTerm ? filteredUsers : filteredUsers.slice(0, 10);
const visiblePolicies = searchTerm ? filteredPolicies : filteredPolicies.slice(0, 10);
const visiblePages = searchTerm ? accessibleItems : accessibleItems.slice(0, 5);
const visibleUsers = searchTerm ? filteredUsers : filteredUsers?.slice(0, 10);
const visiblePolicies = searchTerm ? filteredPolicies : filteredPolicies?.slice(0, 10);
const visiblePages = searchTerm ? accessibleItems : accessibleItems?.slice(0, 5);

return (
<>
Expand All @@ -78,7 +55,7 @@ export function GlobalSearch() {
<CommandInput placeholder="Type to search..." value={searchTerm} onValueChange={setSearchTerm}/>
<CommandList className="max-h-[80vh] overflow-auto">
<CommandEmpty>No results found.</CommandEmpty>
{loading ? (
{isPending ? (
<div className="space-y-1 overflow-hidden px-1 py-2">
<div className="animate-pulse space-y-2">
{[...Array(5)].map((_, i) => (
Expand Down Expand Up @@ -128,7 +105,7 @@ export function GlobalSearch() {

<CommandGroup heading="Leave Policies" className="p-2">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 max-h-[300px] overflow-auto">
{visiblePolicies.map((policy) => (
{visiblePolicies?.map((policy) => (
<CommandItem
key={policy.id}
onSelect={() => onSelect(`/leaves/policies/${policy.id}`)}
Expand Down
6 changes: 3 additions & 3 deletions src/components/sidebar/SidebarAccountDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import {Button} from "@/components/ui/button.tsx";
import {Check, Ellipsis, X} from "lucide-react";
import React, {useContext, useState} from "react";
import {Link, useNavigate} from "react-router-dom";
import {useNavigate} from "react-router-dom";
import {UserContext} from "@/contexts/UserContext.tsx";
import {
Dialog,
Expand All @@ -35,7 +35,7 @@ export default function SidebarAccountDropdown({isActive}: AccountDropdownProps)
};

return (
<Link className='p-4' to={"profile"}>
<div className="p-4">
<DropdownMenu>
<DropdownMenuTrigger className="w-full">
<div
Expand All @@ -60,7 +60,7 @@ export default function SidebarAccountDropdown({isActive}: AccountDropdownProps)
</DropdownMenu>

{signOut && <SignOut signOut={signOut} setSignOut={setSignOut}/>}
</Link>
</div>
)
}

Expand Down
130 changes: 54 additions & 76 deletions src/contexts/UserContext.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {createContext, ReactNode, useCallback, useEffect, useState} from "react";
import {UserResponse} from "@/core/types/user.ts";
import {OrganizationResponse} from "@/core/types/organization.ts";
import {getUser} from "@/core/services/userService";
import {getOrganization} from "@/core/services/organizationService";
import {getErrorMessage} from "@/core/utils/errorHandler.ts";
import { createContext, ReactNode, useCallback, useEffect, useState } from "react";
import { UserResponse } from "@/core/types/user.ts";
import { OrganizationResponse } from "@/core/types/organization.ts";
import { getErrorMessage } from "@/core/utils/errorHandler.ts";
import useLocalStorage from "../hooks/useLocalStorage";
import {toast} from "@/components/ui/use-toast";
import { toast } from "@/components/ui/use-toast";
import { useOrganization } from "@/core/stores/organizationStore.ts";
import {useUser} from "@/core/stores/userStore.ts";

type UserContextType = {
user: UserResponse | null;
Expand All @@ -15,83 +15,61 @@ type UserContextType = {
authenticate: (accessToken: string | null) => void;
signout: () => void;
isAuthenticated: () => boolean;
}
};

export const UserContext = createContext<UserContextType>({
user: null,
setUser: () => {},
organization: null,
accessToken: null,
authenticate: () => { },
signout: () => { },
isAuthenticated: () => false
})
user: null,
setUser: () => {},
organization: null,
accessToken: null,
authenticate: () => {},
signout: () => {},
isAuthenticated: () => false,
});

type UserContextProviderType = {
children: ReactNode;
}
children: ReactNode;
};

export const UserContextProvider = ({ children}: UserContextProviderType) => {
const [accessToken, setAccessToken] = useLocalStorage<string | null>("ACCESS_TOKEN", null)
const [user, setUser] = useState<UserResponse | null>(null);
const [organization, setOrganization] = useState<OrganizationResponse | null>(null);
export const UserContextProvider = ({ children }: UserContextProviderType) => {
const [accessToken, setAccessToken] = useLocalStorage<string | null>("ACCESS_TOKEN", null);
const [user, setUser] = useState<UserResponse | null>(null);
const { data: organization } = useOrganization();
const { data: fetchedUser, error } = useUser();

const isAuthenticated = (): boolean => {
return accessToken != null && accessToken !== "";
};
const isAuthenticated = (): boolean => {
return accessToken != null && accessToken !== "";
};
const isAuth = isAuthenticated();

const authenticate = (_accessToken: string | null) => {
setAccessToken(_accessToken);
};
const authenticate = (_accessToken: string | null) => {
setAccessToken(_accessToken);
};

const signout = () => {
setAccessToken(null);
setUser(null);
setOrganization(null);
console.log("userContext signout")
}
const signout = () => {
setAccessToken(null);
setUser(null);
console.log("userContext signout");
};

useEffect(() => {
if (isAuthenticated()) {
// Fetch user data
getUser()
.then((data: UserResponse) => setUser(data))
.catch((error) => {
const errorMessage = getErrorMessage(error);
toast({
title: "Error",
description: errorMessage,
variant: "destructive",
});
});
useEffect(() => {
if (isAuth && fetchedUser) {
setUser(fetchedUser);
}
if (error) {
toast({title: "Error", description: getErrorMessage(error), variant: "destructive",});
}
}, [fetchedUser, error, isAuth, accessToken]);

// Fetch organization data
getOrganization()
.then((data: OrganizationResponse) => setOrganization(data))
.catch((error) => {
const errorMessage = getErrorMessage(error);
toast({
title: "Error",
description: errorMessage,
variant: "destructive",
});
});
}
}, [accessToken]);
const contextValue = {
user: user,
setUser: setUser,
organization: organization,
accessToken: accessToken,
isAuthenticated: useCallback(() => isAuthenticated(), [accessToken]),
authenticate: useCallback((accessToken: string | null) => authenticate(accessToken), []),
signout: useCallback(() => signout(), []),
};

const contextValue = {
user: user,
setUser: setUser,
organization: organization,
accessToken: accessToken,
isAuthenticated: useCallback(() => isAuthenticated(), [accessToken]),
authenticate: useCallback((accessToken: string | null) => authenticate(accessToken), []),
signout: useCallback(() => signout(), [])
}

return (
<UserContext.Provider value={contextValue}>
{children}
</UserContext.Provider>
)
}
return <UserContext.Provider value={contextValue}>{children}</UserContext.Provider>;
};
Loading