@@ -1,2 +1,3 @@ | |||
API_URL=http://localhost:8090/api | |||
NEXTAUTH_SECRET=secret | |||
NEXTAUTH_SECRET=secret | |||
NEXT_PUBLIC_API_URL=http://localhost:8090/api |
@@ -1,3 +1,4 @@ | |||
API_URL=http://localhost:8090/api | |||
NEXTAUTH_SECRET=secret | |||
NEXTAUTH_URL=https://fpsms-uat.2fi-solutions.com | |||
NEXTAUTH_URL=https://fpsms-uat.2fi-solutions.com | |||
NEXT_PUBLIC_API_URL=http://localhost:8090/api |
@@ -0,0 +1,38 @@ | |||
"use client"; | |||
import React, {createContext, useContext, useEffect, useState} from 'react'; | |||
import axiosInstance, {SetupAxiosInterceptors} from './axiosInstance'; | |||
const AxiosContext = createContext(axiosInstance); | |||
const TokenContext = createContext({ | |||
setAccessToken: (token: string | null) => {}, | |||
}); | |||
export const AxiosProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { | |||
const [accessToken, setAccessToken] = useState<string | null>(localStorage.getItem("accessToken")); | |||
useEffect(() => { | |||
if (accessToken) { | |||
axiosInstance.defaults.headers.Authorization = `Bearer ${accessToken}`; | |||
SetupAxiosInterceptors(accessToken); | |||
console.log("[debug] Updated accessToken:", accessToken); | |||
} | |||
}, [accessToken]); | |||
return ( | |||
<AxiosContext.Provider value={axiosInstance}> | |||
<TokenContext.Provider value={{ setAccessToken }}> | |||
{children} | |||
</TokenContext.Provider> | |||
</AxiosContext.Provider> | |||
); | |||
}; | |||
// Custom hook to use Axios instance | |||
export const useAxios = () => { | |||
return useContext(AxiosContext); | |||
}; | |||
// Custom hook to manage access token | |||
export const useToken = () => { | |||
return useContext(TokenContext); | |||
}; |
@@ -0,0 +1,56 @@ | |||
import axios from 'axios'; | |||
const axiosInstance = axios.create({ | |||
baseURL: process.env.API_URL, | |||
}); | |||
// Clear existing interceptors to prevent multiple registrations | |||
const clearInterceptors = () => { | |||
const requestInterceptor = axiosInstance.interceptors.request.use(); | |||
const responseInterceptor = axiosInstance.interceptors.response.use(); | |||
if (requestInterceptor) { | |||
axiosInstance.interceptors.request.eject(requestInterceptor); | |||
} | |||
if (responseInterceptor) { | |||
axiosInstance.interceptors.response.eject(responseInterceptor); | |||
} | |||
}; | |||
export const SetupAxiosInterceptors = (inputToken: string | null) => { | |||
console.log("[debug] set up interceptors", inputToken); | |||
// Clear existing interceptors | |||
clearInterceptors(); | |||
// Request interceptor | |||
axiosInstance.interceptors.request.use( | |||
(config) => { | |||
// Use the token passed in or retrieve from localStorage | |||
const token = inputToken ?? localStorage.getItem('accessToken'); | |||
if (token) { | |||
config.headers.Authorization = `Bearer ${token}`; | |||
} | |||
return config; | |||
}, | |||
(error) => { | |||
return Promise.reject(error); | |||
} | |||
); | |||
console.log("[debug] set up interceptors2", inputToken); | |||
// Response interceptor | |||
axiosInstance.interceptors.response.use( | |||
(response) => { | |||
return response; | |||
}, | |||
(error) => { | |||
console.error('API Error:', error.response?.data || error); | |||
return Promise.reject(error); | |||
} | |||
); | |||
}; | |||
export default axiosInstance; |
@@ -6,6 +6,7 @@ import Box from "@mui/material/Box"; | |||
import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig"; | |||
import Stack from "@mui/material/Stack"; | |||
import Breadcrumb from "@/components/Breadcrumb"; | |||
import {AxiosProvider} from "@/app/(main)/axios/AxiosProvider"; | |||
export default async function MainLayout({ | |||
children, | |||
@@ -19,23 +20,25 @@ export default async function MainLayout({ | |||
} | |||
return ( | |||
<> | |||
<AppBar | |||
profileName={session.user.name!} | |||
avatarImageSrc={session.user.image || undefined} | |||
/> | |||
<Box | |||
component="main" | |||
sx={{ | |||
marginInlineStart: { xs: 0, xl: NAVIGATION_CONTENT_WIDTH }, | |||
padding: { xs: "1rem", sm: "1.5rem", lg: "3rem" }, | |||
}} | |||
> | |||
<Stack spacing={2}> | |||
<Breadcrumb /> | |||
{children} | |||
</Stack> | |||
</Box> | |||
</> | |||
<AxiosProvider> | |||
<> | |||
<AppBar | |||
profileName={session.user.name!} | |||
avatarImageSrc={session.user.image || undefined} | |||
/> | |||
<Box | |||
component="main" | |||
sx={{ | |||
marginInlineStart: { xs: 0, xl: NAVIGATION_CONTENT_WIDTH }, | |||
padding: { xs: "1rem", sm: "1.5rem", lg: "3rem" }, | |||
}} | |||
> | |||
<Stack spacing={2}> | |||
<Breadcrumb /> | |||
{children} | |||
</Stack> | |||
</Box> | |||
</> | |||
</AxiosProvider> | |||
); | |||
} |
@@ -1,14 +1,17 @@ | |||
"use client"; | |||
import { useCallback, useMemo, useState } from "react"; | |||
import {useCallback, useEffect, useMemo, useState} from "react"; | |||
import SearchBox, { Criterion } from "../SearchBox"; | |||
import { ItemsResult } from "@/app/api/settings/item"; | |||
import {fetchAllItemsByPage, ItemsResult} from "@/app/api/settings/item"; | |||
import { useTranslation } from "react-i18next"; | |||
import SearchResults, { Column } from "../SearchResults"; | |||
import { EditNote } from "@mui/icons-material"; | |||
import { useRouter, useSearchParams } from "next/navigation"; | |||
import { GridDeleteIcon } from "@mui/x-data-grid"; | |||
import { TypeEnum } from "@/app/utils/typeEnum"; | |||
import axios from "axios"; | |||
import {BASE_API_URL, NEXT_PUBLIC_API_URL} from "@/config/api"; | |||
import axiosInstance from "@/app/(main)/axios/axiosInstance"; | |||
type Props = { | |||
items: ItemsResult[]; | |||
@@ -20,6 +23,11 @@ const ItemsSearch: React.FC<Props> = ({ items }) => { | |||
const [filteredItems, setFilteredItems] = useState<ItemsResult[]>(items); | |||
const { t } = useTranslation("items"); | |||
const router = useRouter(); | |||
const [filterObj, setFilterObj] = useState(); | |||
const [pagingController, setPagingController] = useState({ | |||
pageNum: 1, | |||
pageSize: 10, | |||
}) | |||
const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||
() => { | |||
@@ -70,27 +78,61 @@ const ItemsSearch: React.FC<Props> = ({ items }) => { | |||
[filteredItems] | |||
); | |||
const onReset = useCallback(() => { | |||
setFilteredItems(items); | |||
}, [items]); | |||
useEffect(() => { | |||
if (filterObj) { | |||
refetchData(filterObj); | |||
} | |||
}, [filterObj, pagingController]); | |||
const refetchData = async (filterObj: SearchQuery) => { | |||
// Make sure the API endpoint is correct | |||
const params ={ | |||
pageNum: pagingController.pageNum, | |||
pageSize: pagingController.pageSize, | |||
...filterObj, | |||
} | |||
try { | |||
console.log("[debug] axiosInstance", axiosInstance) | |||
const response = await axiosInstance.get<ItemsResult[]>(`${NEXT_PUBLIC_API_URL}/items/getRecordByPage`, { params }); | |||
console.log("[debug] resposne", response) | |||
setFilteredItems(response.data.records); | |||
return response; // Return the data from the response | |||
} catch (error) { | |||
console.error('Error fetching items:', error); | |||
throw error; // Rethrow the error for further handling | |||
} | |||
}; | |||
const onReset = useCallback(() => { | |||
setFilteredItems(items); | |||
}, [items]); | |||
return ( | |||
<> | |||
<SearchBox | |||
criteria={searchCriteria} | |||
onSearch={(query) => { | |||
setFilteredItems( | |||
items.filter((pm) => { | |||
return ( | |||
pm.code.toLowerCase().includes(query.code.toLowerCase()) && | |||
pm.name.toLowerCase().includes(query.name.toLowerCase()) | |||
); | |||
// setFilteredItems( | |||
// items.filter((pm) => { | |||
// return ( | |||
// pm.code.toLowerCase().includes(query.code.toLowerCase()) && | |||
// pm.name.toLowerCase().includes(query.name.toLowerCase()) | |||
// ); | |||
// }) | |||
// ); | |||
// @ts-ignore | |||
setFilterObj({ | |||
...query | |||
}) | |||
); | |||
}} | |||
onReset={onReset} | |||
/> | |||
<SearchResults<ItemsResult> items={filteredItems} columns={columns} /> | |||
<SearchResults<ItemsResult> | |||
items={filteredItems} | |||
columns={columns} | |||
setPagingController={setPagingController} | |||
pagingController={pagingController} | |||
/> | |||
</> | |||
); | |||
}; | |||
@@ -12,6 +12,8 @@ import { useRouter } from "next/navigation"; | |||
import { useEffect, useState } from "react"; | |||
import { SubmitHandler, useForm } from "react-hook-form"; | |||
import { useTranslation } from "react-i18next"; | |||
import {SetupAxiosInterceptors} from "@/app/(main)/axios/axiosInstance"; | |||
import {useToken} from "@/app/(main)/axios/AxiosProvider"; | |||
type LoginFields = { | |||
username: string; | |||
@@ -47,6 +49,7 @@ const LoginForm: React.FC = () => { | |||
const [serverError, setServerError] = useState<string>(); | |||
const router = useRouter(); | |||
const { setAccessToken } = useToken(); | |||
const onSubmit: SubmitHandler<LoginFields> = async (data) => { | |||
const res = await signIn("credentials", { | |||
@@ -62,7 +65,10 @@ const LoginForm: React.FC = () => { | |||
// set auth to local storage | |||
const session = await getSession() as SessionWithAbilities | |||
// @ts-ignore | |||
window.localStorage.setItem("accessToken", session?.accessToken) | |||
setAccessToken(session?.accessToken); | |||
SetupAxiosInterceptors(session?.accessToken); | |||
// console.log(session) | |||
window.localStorage.setItem("abilities", JSON.stringify(session?.abilities)) | |||
@@ -48,6 +48,8 @@ function SearchResults<T extends ResultWithId>({ | |||
items, | |||
columns, | |||
noWrapper, | |||
pagingController, | |||
setPagingController | |||
}: Props<T>) { | |||
const [page, setPage] = React.useState(0); | |||
const [rowsPerPage, setRowsPerPage] = React.useState(10); | |||
@@ -57,6 +59,10 @@ function SearchResults<T extends ResultWithId>({ | |||
newPage, | |||
) => { | |||
setPage(newPage); | |||
setPagingController({ | |||
...pagingController, | |||
pageNum: newPage, | |||
}) | |||
}; | |||
const handleChangeRowsPerPage: TablePaginationProps["onRowsPerPageChange"] = ( | |||
@@ -64,6 +70,10 @@ function SearchResults<T extends ResultWithId>({ | |||
) => { | |||
setRowsPerPage(+event.target.value); | |||
setPage(0); | |||
setPagingController({ | |||
...pagingController, | |||
pageNum: +event.target.value, | |||
}) | |||
}; | |||
const table = ( | |||
@@ -1,2 +1,3 @@ | |||
export const BASE_API_URL = `${process.env.API_URL}`; | |||
export const LOGIN_API_PATH = `${BASE_API_URL}/login`; | |||
export const NEXT_PUBLIC_API_URL= `${process.env.NEXT_PUBLIC_API_URL}`; |