feat: ApiContext

This commit is contained in:
Firman Syah 2026-06-02 13:17:58 +07:00
parent 25c9bc21b3
commit 1797af7ba9
8 changed files with 4444 additions and 4262 deletions

8444
dist/index.js vendored

File diff suppressed because it is too large Load Diff

27
src/App.jsx Normal file
View File

@ -0,0 +1,27 @@
import ListVirtual from "./components/ListVirtual";
import { ApiProvider } from "./components/ApiContext";
import { API_TOKEN } from "./config/constants";
const Testing = () => {
const token = API_TOKEN;
return (
<div style={{ padding: 8, marginTop: 12 }}>
<ApiProvider
headers={{
Authorization: `Bearer ${token}`,
}}
>
<ListVirtual
id="company-select"
label="Company"
name="name"
url="/company"
defaultValue={"442"}
size="small"
/>
</ApiProvider>
</div>
)
}
export default Testing;

View File

@ -1,3 +1,5 @@
// src/api/request.js
import { formatApiResponse } from '../utils/response'; import { formatApiResponse } from '../utils/response';
import { oslogApi, apiGo } from './axiosClient'; import { oslogApi, apiGo } from './axiosClient';
@ -10,31 +12,26 @@ const request = async ({
endpoint = '', endpoint = '',
suffix = '', suffix = '',
data = null, data = null,
params = {},
headers = {},
customUrl = '', customUrl = '',
axiosConfig = {}, axiosConfig = {}
}) => { }) => {
const url = customUrl || `${endpoint}${suffix}`; const url = customUrl || `${endpoint}${suffix}`;
try { try {
const client = isApiGo ? apiGo : oslogApi; const client = isApiGo ? apiGo : oslogApi;
const response = await client({ const response = await client({
method, method,
url, url,
data, data,
params,
headers, // highest priority
...axiosConfig, ...axiosConfig
}); });
return formatApiResponse(response); return formatApiResponse(response);
} catch (error) { } catch (error) {
const responseError = formatApiResponse(error); return formatApiResponse(error);
//logError(`at endpoint: ${url} error: `, responseError);
return responseError;
} }
}; };
@ -42,65 +39,86 @@ const request = async ({
* CREATE NEW * CREATE NEW
* POST /endpoint/new * POST /endpoint/new
*/ */
export const newRequest = (endpoint, data = null, config = {}) => export const newRequest = (
endpoint,
data = null,
axiosConfig = {}
) =>
request({ request({
method: 'post', method: 'post',
endpoint, endpoint,
suffix: '/new', suffix: '/new',
data, data,
...config, axiosConfig
}); });
/** /**
* ADD * ADD
* POST /endpoint/add * POST /endpoint/add
*/ */
export const addRequest = (endpoint, data = null, config = {}) => export const addRequest = (
endpoint,
data = null,
axiosConfig = {}
) =>
request({ request({
method: 'post', method: 'post',
endpoint, endpoint,
suffix: '/add', suffix: '/add',
data, data,
...config, axiosConfig
}); });
/** /**
* GET BY ID * GET BY ID
* GET /endpoint/:id * GET /endpoint/:id
*/ */
export const getByIdRequest = (id, endpoint, params = {}, config = {}) => export const getByIdRequest = (
id,
endpoint,
axiosConfig = {}
) =>
request({ request({
method: 'get', method: 'get',
endpoint, endpoint,
suffix: `/${id}`, suffix: `/${id}`,
params, axiosConfig
...config,
}); });
/** /**
* EDIT * EDIT
* PUT /endpoint/edit/:id * PUT /endpoint/edit/:id
*/ */
export const editRequest = (id, endpoint, data = null, config = {}) => export const editRequest = (
id,
endpoint,
data = null,
axiosConfig = {}
) =>
request({ request({
method: 'put', method: 'put',
endpoint, endpoint,
suffix: `/edit/${id}`, suffix: `/edit/${id}`,
data, data,
...config, axiosConfig
}); });
/** /**
* DELETE * DELETE
* DELETE /endpoint/delete/:id * DELETE /endpoint/delete/:id
*/ */
export const deleteRequest = (id, endpoint, data = null, config = {}) => export const deleteRequest = (
id,
endpoint,
data = null,
axiosConfig = {}
) =>
request({ request({
method: 'delete', method: 'delete',
endpoint, endpoint,
suffix: `/delete/${id}`, suffix: `/delete/${id}`,
data, data,
...config, axiosConfig
}); });
/** /**
@ -110,8 +128,7 @@ export const deleteRequest = (id, endpoint, data = null, config = {}) =>
export const searchRequest = ( export const searchRequest = (
endpoint, endpoint,
data = null, data = null,
params = {}, axiosConfig = {}
config = {},
) => { ) => {
const timezone = -new Date().getTimezoneOffset() / 60; const timezone = -new Date().getTimezoneOffset() / 60;
@ -120,37 +137,38 @@ export const searchRequest = (
endpoint, endpoint,
suffix: '/search', suffix: '/search',
data, data,
axiosConfig: {
...axiosConfig,
params: { params: {
tz: timezone, tz: timezone,
...params, ...(axiosConfig.params || {})
}, }
...config, }
}); });
}; };
/** /**
* CUSTOM REQUEST * CUSTOM REQUEST
* untuk endpoint bebas / beda sendiri
*/ */
export const customRequest = ({ export const customRequest = ({
method = 'get', method = 'get',
url = '', url = '',
data = null, data = null,
params = {}, isApiGo = false,
headers = {}, axiosConfig = {}
axiosConfig = {},
}) => }) =>
request({ request({
method, method,
customUrl: url, customUrl: url,
data, data,
params, isApiGo,
headers, axiosConfig
axiosConfig,
}); });
/** /**
* Optional Export Object * EXPORT OBJECT
*/ */
export const apiRequest = { export const apiRequest = {
new: newRequest, new: newRequest,
@ -159,5 +177,5 @@ export const apiRequest = {
edit: editRequest, edit: editRequest,
delete: deleteRequest, delete: deleteRequest,
search: searchRequest, search: searchRequest,
custom: customRequest, custom: customRequest
}; };

View File

@ -0,0 +1,39 @@
import React, { createContext, useContext } from "react";
// Sentinel value to reliably detect if the hook is called outside the provider
const sentinel = {};
/**
* ApiContext created using the React Context API.
* Defaults to a sentinel value to detect out-of-provider usage.
*/
export const ApiContext = createContext(sentinel);
/**
* ApiProvider component that accepts a `headers` prop and provides it through context.
*
* @param {Object} props
* @param {Object} props.headers - The headers to provide to API requests
* @param {React.ReactNode} props.children - Child components
*/
export function ApiProvider({ headers, children }) {
return React.createElement(
ApiContext.Provider,
{ value: headers },
children
);
}
/**
* Custom hook `useApiHeaders()` that returns the current headers from context.
* Throws a clear error if called outside of `ApiProvider`.
*
* @returns {Object} The current headers from context
*/
export function useApiHeaders() {
const context = useContext(ApiContext);
if (context === sentinel) {
throw new Error("useApiHeaders must be used within an ApiProvider");
}
return context;
}

View File

@ -16,6 +16,7 @@ import {
import ClearIcon from "@mui/icons-material/Clear"; import ClearIcon from "@mui/icons-material/Clear";
import FilterTableBuilder from "../utils/filterTableBuilder"; import FilterTableBuilder from "../utils/filterTableBuilder";
import { apiRequest } from "../api/request"; import { apiRequest } from "../api/request";
import { useApiHeaders } from "./ApiContext";
export default function ListVirtual({ export default function ListVirtual({
id, id,
@ -33,8 +34,8 @@ export default function ListVirtual({
helperText, helperText,
filter, filter,
isApiGo = false, isApiGo = false,
headersRequest,
}) { }) {
const headersRequest = useApiHeaders();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [rows, setRows] = useState([]); const [rows, setRows] = useState([]);
@ -72,11 +73,10 @@ export default function ListVirtual({
const res = await apiRequest.search( const res = await apiRequest.search(
url, url,
payload.build(), payload.build(),
{},
{ {
isApiGo,
headers: headersRequest headers: headersRequest
} },
isApiGo,
); );
const incomingData = res.data || []; const incomingData = res.data || [];
@ -128,11 +128,10 @@ export default function ListVirtual({
const res = await apiRequest.search( const res = await apiRequest.search(
url, url,
payload.build(), payload.build(),
{},
{ {
isApiGo,
headers: headersRequest headers: headersRequest
} },
isApiGo,
); );
const data = res.data?.[0]; const data = res.data?.[0];

2
src/components/index.js Normal file
View File

@ -0,0 +1,2 @@
export { default as ListVirtual } from "./ListVirtual";
export * from "./ApiContext";

View File

@ -1 +1,3 @@
export { default as ListVirtual } from './components/ListVirtual.jsx'; export * from './api/request';
export * from './components';
//export * from './utils';

77
src/main.jsx Normal file
View File

@ -0,0 +1,77 @@
// src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { CssBaseline } from '@mui/material';
import App from './App'
const theme = createTheme({
palette: {
mode: 'light',
primary: {
main: '#2563eb'
},
background: {
default: '#f8fafc'
}
},
shape: {
borderRadius: 12
},
typography: {
fontFamily:
'"Inter", "Roboto", "Helvetica", "Arial", sans-serif',
h5: {
fontWeight: 700
},
button: {
textTransform: 'none',
fontWeight: 600
}
},
components: {
MuiPaper: {
styleOverrides: {
root: {
borderRadius: 16
}
}
},
MuiButton: {
styleOverrides: {
root: {
borderRadius: 10,
paddingInline: 16
}
}
},
MuiOutlinedInput: {
styleOverrides: {
root: {
borderRadius: 12
}
}
}
}
});
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>
</React.StrictMode>
);