diff --git a/package.json b/package.json index 8c524910..e13eb17a 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "react": "^18.2.0", "react-admin": "4.13.1", "react-dom": "^18.2.0", + "react-idle-timer": "^5.7.2", "react-router-dom": "^6.9.0", "soul-cli": "^0.6.1", "vite": "^4.2.0", diff --git a/src/App.tsx b/src/App.tsx index 5e42bd22..7c3ab18a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -71,6 +71,7 @@ import { type AxiosError, isAxiosError } from 'axios' import ChangePassword from './ChangePassword' import { emitter } from './components/Layout/index' import { CHANGE_PASSWORD_EVENT } from './constants' +import { useIdleTimer } from 'react-idle-timer' const style = { backgroundColor: 'white', @@ -115,6 +116,18 @@ function App(): React.ReactElement { }) const [openChangePasswordModal, setOpenChangePasswordModal] = useState(false) + const handleOnIdle = (): void => { + removeUserToken() + } + const handleOnAction = (): void => { + reset() + } + const { reset } = useIdleTimer({ + timeout: 1000 * 60 * 60, + onIdle: handleOnIdle, + onActive: handleOnAction + }) + const { register, handleSubmit, @@ -192,9 +205,9 @@ function App(): React.ReactElement { }) if ( - lastUpdatedAt !== null && + typeof lastUpdatedAt === 'string' && lastUpdatedAt !== '' && - !isDateNotInPastDays(lastUpdatedAt, 1) + !isDateNotInPastDays(lastUpdatedAt, 0) ) throw new Error( 'Password update not allowed. Please wait at least one day before updating your password again.' diff --git a/src/components/ProtectionRefInput.tsx b/src/components/ProtectionRefInput.tsx index fee43902..2bcac0d2 100644 --- a/src/components/ProtectionRefInput.tsx +++ b/src/components/ProtectionRefInput.tsx @@ -17,9 +17,11 @@ import { useGetList, useRecordContext, useResourceContext, - type Identifier + type Identifier, + useRedirect } from 'react-admin' import { Box } from '@mui/system' +import { R_RICH_ITEMS } from '../constants' interface Props { reference: string @@ -66,6 +68,8 @@ export default function ProtectionRefInput< getValues, reset } = useFormContext() + const redirect = useRedirect() + const [valueLabel, setValueLabel] = useState('') type SelectedIdType = T['id'] | Array @@ -163,6 +167,10 @@ export default function ProtectionRefInput< if (record?.id) { // onValueChange(getPreviousValue()) updateRecord(record.id as number, selectedData as number[]) + .then(() => { + redirect(`/${R_RICH_ITEMS}/${record?.id}/show`) + }) + .catch(console.log) } else if (id) { createRecord(id, selectedData as number[]) } diff --git a/src/constants.ts b/src/constants.ts index 89972ead..cb1c234e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -18,7 +18,6 @@ export const MUTATION_MODE = 'optimistic' // session storage value. export const SESSION_LOGIN = 'login' - // major table/resource names export const R_USERS = 'user' export const R_BATCHES = 'batch' @@ -110,8 +109,6 @@ export const ITEM_SAVE = 'item_save' export const CHANGE_PASSWORD_EVENT = 'change_password' - - export const cosmeticLabels = { [R_USERS]: 'User', [R_ITEMS]: 'Item', @@ -127,6 +124,5 @@ export const cosmeticLabels = { [R_CAT_CODE]: 'Cat Code', [R_CAT_HANDLE]: 'Cat Handle', [R_DEPARTMENT]: 'Department', - [R_PROTECTIVE_MARKING]: 'Protective Marking', - -} \ No newline at end of file + [R_PROTECTIVE_MARKING]: 'Protective Marking' +} diff --git a/src/hooks/useRefTable.ts b/src/hooks/useRefTable.ts index 7a3366e4..5f105414 100644 --- a/src/hooks/useRefTable.ts +++ b/src/hooks/useRefTable.ts @@ -1,14 +1,9 @@ -import { - useCreate, - useDataProvider, - // useDeleteMany, - useRedirect -} from 'react-admin' -import { R_ITEMS } from '../constants' +import { useCreate, useDataProvider, useRedirect } from 'react-admin' +import * as constants from '../constants' interface DBMethods { createRecord: (id: number, data?: number[]) => void - updateRecord: (id: number, data?: number[]) => void + updateRecord: (id: number, data?: number[]) => Promise } export default function useRefTable( @@ -17,32 +12,43 @@ export default function useRefTable( resource: string ): DBMethods { const [create] = useCreate() - // const [deleteMany] = useDeleteMany() - const redirect = useRedirect() const dataProvider = useDataProvider() + const redirect = useRedirect() - const updateRecord = (id: number, data?: number[]): void => { - dataProvider - .getList(refTable, { - pagination: { page: 1, perPage: 100 }, - sort: { field: 'id', order: 'ASC' }, - filter: { [resource]: id } - }) - .then(({ data: tableData = [] }) => { - const idsToDelete = tableData.map( - (item: Record) => item.id - ) - - if (idsToDelete.length > 0) - {dataProvider.deleteMany(refTable, { ids: idsToDelete }) - .then(() => { + const updateRecord = async ( + id: number, + data?: number[] + ): Promise => { + return await new Promise((resolve, reject): void => { + dataProvider + .getList(refTable, { + pagination: { page: 1, perPage: 100 }, + sort: { field: 'id', order: 'ASC' }, + filter: { [resource]: id } + }) + .then(({ data: tableData = [] }) => { + const idsToDelete = tableData.map( + (item: Record) => item.id + ) + if (idsToDelete.length > 0) { + dataProvider + .deleteMany(refTable, { ids: idsToDelete }) + .then(() => { + createRecord(id, data) + resolve(null) + }) + .catch((err) => { + reject(err) + }) + } else { createRecord(id, data) - }) - .catch(console.log) - } - else createRecord(id, data) - }) - .catch(console.log) + resolve(null) + } + }) + .catch((err) => { + reject(err) + }) + }) } const createRecord = (id: number, data?: number[]): void => { @@ -55,7 +61,7 @@ export default function useRefTable( } }) }) - if (resource !== R_ITEMS) redirect(`/${resource}`) + if (resource !== constants.R_ITEMS) redirect(`/${resource}`) } catch (error: any) { console.log(error) } diff --git a/src/providers/authProvider/index.ts b/src/providers/authProvider/index.ts index 798b858e..db60e693 100644 --- a/src/providers/authProvider/index.ts +++ b/src/providers/authProvider/index.ts @@ -39,7 +39,7 @@ const getCookie = (name: string): string | null => { const setToken = (token: string): void => { const date = new Date() - date.setTime(date.getTime() + 1 * 60 * 60 * 1000) + date.setTime(date.getTime() + 24 * 60 * 60 * 1000) const expires = date.toUTCString() document.cookie = `${constants.TOKEN_KEY}=${token}; expires=${expires}; path=/ ` } diff --git a/src/providers/authProvider/permissions.ts b/src/providers/authProvider/permissions.ts index 9389ea4e..6298b9cf 100644 --- a/src/providers/authProvider/permissions.ts +++ b/src/providers/authProvider/permissions.ts @@ -5,7 +5,7 @@ const basePermissions = { [constants.R_BATCHES]: { read: true, write: true, delete: false }, [constants.R_ITEMS]: { read: true, write: true, delete: false }, [constants.R_ALL_ITEMS]: { read: true, write: true, delete: false }, - [constants.R_USERS]: { read: true, write: true, delete: false }, + [constants.R_USERS]: { read: true, write: false, delete: false }, [constants.R_PLATFORMS]: { read: true, write: false, delete: false }, [constants.R_VAULT_LOCATION]: { read: true, write: false, delete: false }, [constants.R_ADDRESSES]: { read: true, write: true, delete: false }, @@ -20,6 +20,7 @@ const permissions: Record = { 'rco-power-user': { ...basePermissions, [constants.R_PLATFORMS]: { read: true, write: true, delete: false }, + [constants.R_USERS]: { read: true, write: true, delete: false }, [constants.R_VAULT_LOCATION]: { read: true, write: true, delete: false }, 'reference-data': { read: true, write: true, delete: false } } diff --git a/src/providers/dataProvider/index.ts b/src/providers/dataProvider/index.ts index 27c74992..bd66d80d 100644 --- a/src/providers/dataProvider/index.ts +++ b/src/providers/dataProvider/index.ts @@ -90,6 +90,11 @@ export const getDataProvider = async ( const operators = ['_neq', '_eq', '_lte', '_gte'] const SEARCH_OPERATOR = 'q' const nullOperators = ['__null', '__notnull'] +const bridgingTables = [ + constants.R_ITEMS_CODE, + constants.R_ITEMS_CAVE, + constants.R_ITEMS_HANDLE +] export const dataProvider = (apiUrl: string): DataProvider => ({ getList: async (resource: string, params: any) => { @@ -325,25 +330,26 @@ export const dataProvider = (apiUrl: string): DataProvider => ({ }) }, - // Note: Deletion is not supported - delete: async (_resource: string, _params: any) => { - throw new Error('Deletion is not supported!') - // const url = `${apiUrl}/${resource}/rows/${params.id}` - - // return await axios.delete(url).then(() => { - // return { data: params.id } - // }) + // Note: Deletion is not supported (except for bridging tables) + delete: async (resource: string, params: any) => { + if (bridgingTables.includes(resource)) { + const url = `${apiUrl}/${resource}/rows/${params.id}` + return await axios.delete(url).then(() => ({ data: params.id })) + } else { + throw new Error('Deletion is not supported!') + } }, - // Note: Deletion is not supported - deleteMany: async (_resource: string, _params: any) => { - throw new Error('Deletion is not supported!') - - // const ids = params.ids.toString() - // const url = `${apiUrl}/${resource}/rows/${ids}` - - // return await axios.delete(url).then(() => { - // return { data: params.ids } - // }) + // Note: Deletion is not supported (except for bridging tables) + deleteMany: async (resource: string, params: any) => { + if (bridgingTables.includes(resource)) { + const ids = params.ids.toString() + const url = `${apiUrl}/${resource}/rows/${ids}` + return await axios.delete(url).then(() => { + return { data: params.ids } + }) + } else { + throw new Error('Deletion is not supported!') + } } }) diff --git a/src/resources/dispatch/DispatchForm.tsx b/src/resources/dispatch/DispatchForm.tsx index d6c7b02c..38752623 100644 --- a/src/resources/dispatch/DispatchForm.tsx +++ b/src/resources/dispatch/DispatchForm.tsx @@ -77,7 +77,7 @@ export default function DispatchForm(props: Props): React.ReactElement { inputProps={{ helperText: ( <> - View{' '} + Manage{' '} Addresses diff --git a/src/resources/items/ItemForm/ItemFormToolbar.tsx b/src/resources/items/ItemForm/ItemFormToolbar.tsx index ebb0aed0..34eb2394 100644 --- a/src/resources/items/ItemForm/ItemFormToolbar.tsx +++ b/src/resources/items/ItemForm/ItemFormToolbar.tsx @@ -37,11 +37,10 @@ interface ActionsProps { const Actions = (props: ActionsProps): React.ReactElement => { const { onSuccess, setOpenRemarks, vLocationAudits } = props - const redirect = useRedirect() + const onSuccessWithRemarksClose = (data: any): void => { onSuccess(data) setOpenRemarks(false) - redirect(`/${constants.R_RICH_ITEMS}/${data?.id}/show`) } return ( diff --git a/src/resources/users/UserShow.tsx b/src/resources/users/UserShow.tsx index 65195ee7..bb8f8e75 100644 --- a/src/resources/users/UserShow.tsx +++ b/src/resources/users/UserShow.tsx @@ -375,23 +375,21 @@ export default function UserShow(): React.ReactElement { - - { - if (record) { - navigate( - `/audit?filter=${JSON.stringify({ - resource: constants.R_USERS, - dataId: record.id ?? '' - })}` - ) - } - }} - /> - - ) + + {hasWriteAccess && } + { + if (record) { + navigate( + `/audit?filter=${JSON.stringify({ + resource: constants.R_USERS, + dataId: record.id ?? '' + })}` + ) + } + }} + /> + }> diff --git a/yarn.lock b/yarn.lock index 6545f5e4..eb7e5267 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6705,6 +6705,11 @@ react-hook-form@^7.43.9: resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.45.4.tgz#73d228b704026ae95d7e5f7b207a681b173ec62a" integrity sha512-HGDV1JOOBPZj10LB3+OZgfDBTn+IeEsNOKiq/cxbQAIbKaiJUe/KV8DBUzsx0Gx/7IG/orWqRRm736JwOfUSWQ== +react-idle-timer@^5.7.2: + version "5.7.2" + resolved "https://registry.yarnpkg.com/react-idle-timer/-/react-idle-timer-5.7.2.tgz#f506db28a86645dd1b87987116501703e512142b" + integrity sha512-+BaPfc7XEUU5JFkwZCx6fO1bLVK+RBlFH+iY4X34urvIzZiZINP6v2orePx3E6pAztJGE7t4DzvL7if2SL/0GQ== + react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"