diff --git a/public/locales/en/translations.json b/public/locales/en/translations.json index 4871139..7e92947 100644 --- a/public/locales/en/translations.json +++ b/public/locales/en/translations.json @@ -37,5 +37,15 @@ "IncorrectCredentials": "Incorrect credentials.", "Hello": "Hi, {{username}}", "AuthenticationDate": "Authentication date" + }, + "Resource": { + "Code": "Code", + "Name": "Name", + "Category": "Category", + "Secured": "Secured", + "CodeContains": "Code contains: {{value}}", + "NameContains": "Name contains: {{value}}", + "CategoryChip": "Category: {{value}}", + "ListTitle": "Resource management" } } diff --git a/src/components/LoadingContent/LoadingText.js b/src/components/LoadingContent/LoadingText.js new file mode 100644 index 0000000..215fc85 --- /dev/null +++ b/src/components/LoadingContent/LoadingText.js @@ -0,0 +1,48 @@ +import React from "react"; +import PropTypes from "prop-types"; +import grey from "@material-ui/core/colors/grey"; +import { Paper, makeStyles } from "@material-ui/core"; + +const styles = { + cover: { + width: "100%" + }, + bar: { + backgroundColor: grey[200], + height: 10, + margin: 20, + "&:nth-child(2n)": { + marginRight: "20%" + } + }, + paper: { + padding: "10px", + borderRadius: "6px" + } +}; + +const useStyles = makeStyles(styles); + +const LoadingText = ({ lines = 4, onPaper = false, ...props }) => { + const classes = useStyles(); + const loadingText = ( +
+ {[...Array(lines)].map((_e, i) => ( +
+ ))} +
+ ); + + if (onPaper) { + return {loadingText}; + } + + return loadingText; +}; + +LoadingText.propTypes = { + lines: PropTypes.number, + onPaper: PropTypes.bool +}; + +export default LoadingText; diff --git a/src/components/index.js b/src/components/index.js new file mode 100644 index 0000000..963082e --- /dev/null +++ b/src/components/index.js @@ -0,0 +1,3 @@ +import LoadingText from "./LoadingContent/LoadingText"; + +export { LoadingText }; diff --git a/src/features/resources/components/ResourcesContainer.js b/src/features/resources/components/ResourcesContainer.js index a3e1197..8fb4be9 100644 --- a/src/features/resources/components/ResourcesContainer.js +++ b/src/features/resources/components/ResourcesContainer.js @@ -1,12 +1,14 @@ import React, { useState, useEffect, useMemo, useCallback } from "react"; import { Checkbox, FormLabel } from "@material-ui/core"; import MUIDataTable, { debounceSearchRender } from "mui-datatables"; -import Skeleton from "@material-ui/lab/Skeleton"; +import { LoadingText } from "../../../components"; import PageTitle from "../../../components/PageTitle"; import { useResourcesApi, useDictionariesApi } from "../../../api"; import { defaultResourcesFilters } from "../../../constants/resourcesConstants"; +import { useTranslation } from "react-i18next"; const __ROWS_PER_PAGE_OPTIONS = [10, 20, 50, 100]; +const __RESOURCE_NAME_MAX_LENGTH = 35; const ResourcesContainer = () => { const [state, setState] = useState({ @@ -18,6 +20,7 @@ const ResourcesContainer = () => { const [filters, setFilters] = useState({ ...defaultResourcesFilters }); const [resourceCategories, setResourceCategories] = useState([]); + const { t } = useTranslation(); const { getResources } = useResourcesApi(); const { getResourceCategories } = useDictionariesApi(); @@ -52,10 +55,15 @@ const ResourcesContainer = () => { }); }, [getResources, filters]); + const categoryFilterOptions = useMemo( + () => resourceCategories.map((z) => z.categoryName), + [resourceCategories] + ); + const columns = useMemo( () => [ { - label: "Code", + label: t("Resource.Code"), name: "resourceCode", options: { display: true, @@ -65,7 +73,7 @@ const ResourcesContainer = () => { searchable: true, sort: true, customFilterListOptions: { - render: (v) => (v ? `Code contains: ${v}` : []) + render: (v) => (v ? t("Resource.CodeContains", { value: v }) : []) }, filter: true, filterType: "textField", @@ -77,7 +85,7 @@ const ResourcesContainer = () => { } }, { - label: "Name", + label: t("Resource.Name"), name: "resourceName", options: { display: true, @@ -86,8 +94,17 @@ const ResourcesContainer = () => { print: false, searchable: true, sort: true, + customBodyRenderLite: (dataIndex, _rowIndex) => { + if (loading) return; + const value = state.values[dataIndex].resourceName; + const formattedValue = + value.length > __RESOURCE_NAME_MAX_LENGTH + ? `${value.substring(0, __RESOURCE_NAME_MAX_LENGTH + 1)}[...]` + : value; + return formattedValue; + }, customFilterListOptions: { - render: (v) => (v ? `Name contains: ${v}` : []) + render: (v) => (v ? t("Resource.NameContains", { value: v }) : []) }, filter: true, filterType: "textField", @@ -99,7 +116,7 @@ const ResourcesContainer = () => { } }, { - label: "Category", + label: t("Resource.Category"), name: "categoryName", options: { display: true, @@ -109,16 +126,19 @@ const ResourcesContainer = () => { searchable: true, sort: true, filter: true, + customFilterListOptions: { + render: (v) => t("Resource.CategoryChip", { value: v }) + }, filterType: "dropdown", filterOptions: { + names: categoryFilterOptions, logic: (_prop, _filterValue, _row) => false }, - filterList: [], hint: undefined } }, { - label: "Secured", + label: t("Resource.Secured"), name: "secured", options: { display: true, @@ -140,7 +160,7 @@ const ResourcesContainer = () => { customFilterListOptions: { render: (v) => { const checked = v[0]; - return checked ? Secured : []; + return checked ? {t("Resource.Secured")} : []; } }, filter: true, @@ -150,7 +170,7 @@ const ResourcesContainer = () => { display: (filterList, onChange, index, column) => { return (
- Secured + {t("Resource.Secured")} { ); } }, - filterList: undefined, hint: undefined } } ], - [] + [loading, state.values, categoryFilterOptions, t] ); const changeFilters = useCallback((obj) => { @@ -182,21 +201,39 @@ const ResourcesContainer = () => { setFilters((prev) => ({ ...prev, ...obj })); }, []); + const getCategoryId = useCallback( + (name) => { + const category = resourceCategories.find((z) => z.categoryName === name); + return category?.categoryId; + }, + [resourceCategories] + ); + const handleFilterChange = useCallback( - (changedColumn, filterList, type, changedColumnIndex, displayData) => { + (changedColumn, filterList, type, changedColumnIndex, _displayData) => { if (type === "reset") { changeFilters(defaultResourcesFilters); return; } + const filterValue = filterList[changedColumnIndex][0]; + + if (changedColumn === "categoryName") { + const categoryId = filterValue + ? getCategoryId(filterValue) + : filterValue; + changeFilters({ categoryId }); + return; + } + changeFilters({ [changedColumn]: filterValue }); }, - [changeFilters] + [changeFilters, getCategoryId] ); return ( <> - + { textLabels: { body: { noMatch: loading ? ( - + ) : ( - "Sorry, there is no matching data to display" + "Sorry, no matching records found" ) } }