Compare commits

...

3 Commits

Author SHA1 Message Date
Tudor Stanciu f96fe1164b remove unused grid 2022-12-08 00:55:15 +02:00
Tudor Stanciu e3fad73d40 resources page 2022-12-08 00:53:39 +02:00
Tudor Stanciu 74689dbc93 resources list 2022-12-05 03:00:33 +02:00
8 changed files with 339 additions and 63 deletions

View File

@ -1,3 +1,4 @@
import useDictionariesApi from "./useDictionariesApi";
import useResourcesApi from "./useResourcesApi"; import useResourcesApi from "./useResourcesApi";
export { useResourcesApi }; export { useDictionariesApi, useResourcesApi };

View File

@ -0,0 +1,26 @@
import { useCallback } from "react";
import useHttpRequest from "./useHttpRequest";
import { get } from "../utils/axios";
const cdn = process.env.REACT_APP_CDN_URL;
const endpoints = {
mimeTypes: `${cdn}/admin/mime-types`
};
const useDictionariesApi = () => {
const { exec } = useHttpRequest();
const getMimeTypes = useCallback(
(options) => {
const promise = exec(() => get(endpoints.mimeTypes), options);
return promise;
},
[exec]
);
return {
getMimeTypes
};
};
export default useDictionariesApi;

47
src/api/useHttpRequest.js Normal file
View File

@ -0,0 +1,47 @@
import { useCallback, useMemo } from "react";
import { useToast } from "../context/ToastContext";
const useHttpRequest = () => {
const { error } = useToast();
const handleError = useCallback(
(err) => {
const message = `${err.title} ${err.correlationId}`;
error(message);
},
[error]
);
const defaultOptions = useMemo(
() => ({
onStart: () => {},
onCompleted: () => {},
onError: handleError
}),
[handleError]
);
const exec = useCallback(
async (request, options) => {
const internalOptions = { ...defaultOptions, ...options };
const { onStart, onCompleted, onError } = internalOptions;
try {
onStart();
const result = await request();
onCompleted(result);
return result;
} catch (error) {
onError(error);
throw error;
}
},
[defaultOptions]
);
return {
exec
};
};
export default useHttpRequest;

View File

@ -1,59 +1,48 @@
import { useCallback, useMemo } from "react"; import { useCallback } from "react";
import { useToast } from "../context/ToastContext"; import useHttpRequest from "./useHttpRequest";
import { get, post } from "../utils/axios"; import { get } from "../utils/axios";
import { defaultResourcesFilters } from "../constants/resourcesConstants";
const cdn = process.env.REACT_APP_CDN_URL; const cdn = process.env.REACT_APP_CDN_URL;
const endpoints = {
mimeTypes: `${cdn}/admin/mime-types`, const getResourcesEndpoint = (filters) => {
resources: `${cdn}/admin/resources` const {
page,
pageSize,
sortBy,
sortDirection,
fullTextSearch,
resourceCode,
resourceName,
categoryId,
secured
} = filters;
let endpoint = `${cdn}/admin/resources?Page=${page}&PageSize=${pageSize}`;
if (sortBy) endpoint += `&SortBy=${sortBy}`;
if (sortDirection) endpoint += `&SortDirection=${sortDirection}`;
if (fullTextSearch) endpoint += `&FullTextSearch=${fullTextSearch}`;
if (resourceCode) endpoint += `&ResourceCode=${resourceCode}`;
if (resourceName) endpoint += `&ResourceName=${resourceName}`;
if (categoryId) endpoint += `&CategoryId=${categoryId}`;
if (secured) endpoint += `&Secured=${secured}`;
return endpoint;
}; };
const useResourcesApi = () => { const useResourcesApi = () => {
const { error } = useToast(); const { exec } = useHttpRequest();
const handleError = useCallback( const getResources = useCallback(
(err) => { (filters, options) => {
const message = `${err.title} ${err.correlationId}`; const input = { ...defaultResourcesFilters, ...filters };
error(message); const endpoint = getResourcesEndpoint(input);
}, const promise = exec(() => get(endpoint), options);
[error]
);
const defaultOptions = useMemo(
() => ({
onCompleted: () => {},
onError: handleError
}),
[handleError]
);
const call = useCallback(
async (request, options) => {
const internalOptions = { ...defaultOptions, ...options };
const { onCompleted, onError } = internalOptions;
try {
const result = await request();
onCompleted(result);
return result;
} catch (error) {
onError(error);
throw error;
}
},
[defaultOptions]
);
const getMimeTypes = useCallback(
(options = defaultOptions) => {
const promise = call(() => get(endpoints.mimeTypes), options);
return promise; return promise;
}, },
[call, defaultOptions] [exec]
); );
return { return {
getMimeTypes getResources
}; };
}; };

View File

@ -33,7 +33,7 @@ const Content = () => {
[classes.contentShift]: layoutState.isSidebarOpened [classes.contentShift]: layoutState.isSidebarOpened
})} })}
> >
<div className={classes.fakeToolbar} /> <div id="fakeToolbar" className={classes.fakeToolbar} />
<Switch> <Switch>
<Route path="/dashboard" component={Dashboard} /> <Route path="/dashboard" component={Dashboard} />
<Route path="/resources" component={ResourcesContainer} /> <Route path="/resources" component={ResourcesContainer} />

View File

@ -1,20 +1,20 @@
import { makeStyles } from "@material-ui/styles"; import { makeStyles } from "@material-ui/styles";
export default makeStyles(theme => ({ export default makeStyles((theme) => ({
pageTitleContainer: { pageTitleContainer: {
display: "flex", display: "flex",
justifyContent: "space-between", justifyContent: "space-between",
marginBottom: theme.spacing(4), marginBottom: theme.spacing(2),
marginTop: theme.spacing(5), marginTop: theme.spacing(0)
}, },
typo: { typo: {
color: theme.palette.text.hint, color: theme.palette.text.hint
}, },
button: { button: {
boxShadow: theme.customShadows.widget, boxShadow: theme.customShadows.widget,
textTransform: "none", textTransform: "none",
"&:active": { "&:active": {
boxShadow: theme.customShadows.widgetWide, boxShadow: theme.customShadows.widgetWide
}, }
}, }
})); }));

View File

@ -0,0 +1,13 @@
const defaultResourcesFilters = Object.freeze({
page: 1,
pageSize: 10,
sortBy: null,
sortDirection: null,
fullTextSearch: null,
resourceCode: null,
resourceName: null,
categoryId: null,
secured: null
});
export { defaultResourcesFilters };

View File

@ -1,20 +1,220 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useMemo, useCallback } from "react";
import { Grid, Checkbox, CircularProgress, FormLabel } from "@material-ui/core";
import MUIDataTable, { debounceSearchRender } from "mui-datatables";
import PageTitle from "../../../components/PageTitle";
import { useResourcesApi } from "../../../api"; import { useResourcesApi } from "../../../api";
import { defaultResourcesFilters } from "../../../constants/resourcesConstants";
const __ROWS_PER_PAGE_OPTIONS = [10, 20, 50, 100];
const ResourcesContainer = () => { const ResourcesContainer = () => {
const [state, setState] = useState({ loaded: false }); const [state, setState] = useState({ loaded: false, loadedPages: null });
const { getMimeTypes } = useResourcesApi(); const [filters, setFilters] = useState({ ...defaultResourcesFilters });
const { getResources } = useResourcesApi();
useEffect(() => { useEffect(() => {
getMimeTypes({ if (state.loadedPages && state.loadedPages.includes(filters.page)) return;
onCompleted: (data) => {
const dta = Object.assign(data, { loaded: true }); getResources(filters, {
setState(dta); onCompleted: (resources) => {
const data = { ...resources, loaded: true };
setState((prev) => ({
...prev,
...data,
values: prev?.values ? [...prev.values, ...data.values] : data.values,
loadedPages: prev?.loadedPages
? [...prev.loadedPages, data.page]
: [data.page]
}));
} }
}); });
}, [getMimeTypes]); }, [getResources, filters, state.loadedPages]);
return <div>I'm alive!</div>; const columns = useMemo(
() => [
{
label: "Code",
name: "resourceCode",
options: {
display: true,
draggable: true,
download: false,
print: false,
searchable: true,
sort: true,
customFilterListOptions: {
render: (v) => (v ? `Code contains: ${v}` : [])
},
filter: true,
filterType: "textField",
filterOptions: {
logic: (_prop, _filterValue, _row) => false
},
filterList: undefined,
hint: undefined
}
},
{
label: "Name",
name: "resourceName",
options: {
display: true,
draggable: true,
download: false,
print: false,
searchable: true,
sort: true,
customFilterListOptions: {
render: (v) => (v ? `Name contains: ${v}` : [])
},
filter: true,
filterType: "textField",
filterOptions: {
logic: (_prop, _filterValue, _row) => false
},
filterList: undefined,
hint: undefined
}
},
{
label: "Category",
name: "categoryId",
options: {
display: true,
draggable: true,
download: false,
print: false,
searchable: true,
sort: true,
filter: true,
filterType: "dropdown",
filterOptions: {
logic: (_prop, _filterValue, _row) => false
},
filterList: [],
hint: undefined
}
},
{
label: "Secured",
name: "secured",
options: {
display: true,
draggable: true,
download: false,
print: false,
searchable: true,
sort: true,
customBodyRender: (value, _tableMeta, _updateValue) => {
return (
<Checkbox
disabled
size="small"
checked={value}
style={{ padding: "0" }}
/>
);
},
customFilterListOptions: {
render: (v) => {
const checked = v[0];
return checked ? <span>Secured</span> : [];
}
},
filter: true,
filterType: "custom",
filterOptions: {
logic: (_prop, _filterValue, _row) => false,
display: (filterList, onChange, index, column) => {
return (
<div>
<FormLabel>Secured</FormLabel>
<Checkbox
color="primary"
checked={filterList[index][0] || false}
onChange={(event) => {
filterList[index][0] = event.target.checked;
onChange(filterList[index], index, column);
}}
/>
</div>
);
}
},
filterList: undefined,
hint: undefined
}
}
],
[]
);
const handleResetFilters = useCallback(() => {
setFilters((prev) => ({ ...prev, ...defaultResourcesFilters }));
}, [setFilters]);
const handleFilterChange = useCallback(
(changedColumn, filterList, type, changedColumnIndex, displayData) => {
if (type === "reset") {
handleResetFilters();
return;
}
const filterValue = filterList[changedColumnIndex][0];
setFilters((prev) => ({
...prev,
[changedColumn]: filterValue
}));
},
[handleResetFilters]
);
return (
<>
<PageTitle title="ResourcesX" />
{state.loaded === false ? (
<CircularProgress size={26} />
) : (
<MUIDataTable
title="Resources"
columns={columns}
data={state.values ?? []}
options={{
filterType: "textField",
expandableRows: false,
print: false,
selectableRows: "none",
rowsPerPage: state.pageSize,
rowsPerPageOptions: __ROWS_PER_PAGE_OPTIONS,
count: state.totalCount,
customSearchRender: debounceSearchRender(500),
onChangePage: (currentPage) => {
setFilters((prev) => ({ ...prev, page: currentPage + 1 }));
},
onChangeRowsPerPage: (numberOfRows) => {
setFilters((prev) => ({ ...prev, pageSize: numberOfRows }));
},
onColumnSortChange: (changedColumn, direction) =>
setFilters((prev) => ({
...prev,
sortBy: changedColumn,
sortDirection: direction
})),
onFilterChange: handleFilterChange,
onSearchChange: (text) =>
setFilters((prev) => ({ ...prev, fullTextSearch: text })),
customSort: (data) => data,
customSearch: () => true,
setFilterChipProps: (_colIndex, _colName, _data) => {
return {
color: "primary",
variant: "outlined"
};
}
}}
/>
)}
</>
);
}; };
export default ResourcesContainer; export default ResourcesContainer;