diff --git a/api/src/paths/administrative/security/index.ts b/api/src/paths/administrative/security/index.ts index 06df57f6d..7a90e61b4 100644 --- a/api/src/paths/administrative/security/index.ts +++ b/api/src/paths/administrative/security/index.ts @@ -19,37 +19,17 @@ export const GET: Operation = [ ] }; }), - () => {} + getActiveSecurityRules() ]; GET.apiDoc = { - description: 'Get all observations for the survey.', - tags: ['observation'], + description: 'Get all active security rules.', + tags: ['security'], security: [ { Bearer: [] } ], - parameters: [ - { - in: 'path', - name: 'projectId', - schema: { - type: 'number', - minimum: 1 - }, - required: true - }, - { - in: 'path', - name: 'surveyId', - schema: { - type: 'number', - minimum: 1 - }, - required: true - } - ], responses: { 200: { description: 'Security Rules.', @@ -73,7 +53,8 @@ GET.apiDoc = { type: 'string' }, record_end_date: { - type: 'string' + type: 'string', + nullable: true }, create_date: { type: 'string' @@ -82,13 +63,15 @@ GET.apiDoc = { type: 'number' }, update_date: { - type: 'string' + type: 'string', + nullable: true }, update_user: { - type: 'number' + type: 'number', + nullable: true }, revision_count: { - type: 'string' + type: 'number' } } } @@ -120,7 +103,12 @@ export function getActiveSecurityRules(): RequestHandler { const service = new SecurityService(connection); try { + await connection.open(); + const data = await service.getActiveSecurityRules(); + + await connection.commit(); + return res.status(200).json(data); } catch (error) { defaultLog.error({ label: 'getActiveSecurityRules', message: 'error', error }); diff --git a/api/src/repositories/security-repository.ts b/api/src/repositories/security-repository.ts index e7d86b5fb..8a7932af3 100644 --- a/api/src/repositories/security-repository.ts +++ b/api/src/repositories/security-repository.ts @@ -21,7 +21,7 @@ export const SecurityRuleRecord = z.object({ name: z.string(), description: z.string(), record_effective_date: z.string(), - record_end_date: z.string(), + record_end_date: z.string().nullable(), create_date: z.string(), create_user: z.number(), update_date: z.string().nullable(), @@ -248,8 +248,11 @@ export class SecurityRepository extends BaseRepository { async getActiveSecurityRules(): Promise { defaultLog.debug({ label: 'getSecurityRules' }); - const sql = SQL`SELECT * FROM security_rule WHERE record_end_date IS NULL;`; + const sql = SQL` + SELECT * FROM security_rule WHERE record_end_date IS NULL; + `; const response = await this.connection.sql(sql, SecurityRuleRecord); + console.log(response.rows); return response.rows; } } diff --git a/app/src/components/security/SecuritiesDialog.tsx b/app/src/components/security/SecuritiesDialog.tsx index 3cc69b30e..462623431 100644 --- a/app/src/components/security/SecuritiesDialog.tsx +++ b/app/src/components/security/SecuritiesDialog.tsx @@ -1,4 +1,5 @@ import EditDialog from 'components/dialog/EditDialog'; +import { ISecurityRule } from 'hooks/api/useSecurityApi'; import yup from 'utils/YupSchema'; import SecurityRuleForm from './SecurityRuleForm'; interface ISecuritiesDialogProps { @@ -6,9 +7,15 @@ interface ISecuritiesDialogProps { onClose: () => void; } -const SecuritiesDialog = (props: ISecuritiesDialogProps) => { - const SecurityRuleYupSchema = yup.array(yup.number()); +export const SecurityRuleYupSchema = yup.object().shape({ + rules: yup.array(yup.object()).min(1) +}); + +export interface ISecurityRuleFormProps { + rules: ISecurityRule[]; +} +const SecuritiesDialog = (props: ISecuritiesDialogProps) => { return ( <> { onSave={() => console.log('SAVE SOME SECURITY RULES SON')} component={{ element: , - initialValues: [], + initialValues: { rules: [] }, validationSchema: SecurityRuleYupSchema }} /> diff --git a/app/src/components/security/SecurityRuleForm.tsx b/app/src/components/security/SecurityRuleForm.tsx index 6e29c25f3..ac16f9ad5 100644 --- a/app/src/components/security/SecurityRuleForm.tsx +++ b/app/src/components/security/SecurityRuleForm.tsx @@ -1,30 +1,26 @@ -import { mdiMagnify } from '@mdi/js'; +import { mdiClose, mdiMagnify } from '@mdi/js'; import Icon from '@mdi/react'; -import { Alert, AlertTitle, Typography } from '@mui/material'; -import Autocomplete from '@mui/material/Autocomplete'; +import { Alert, AlertTitle, IconButton, Paper, Typography } from '@mui/material'; +import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete'; import Box from '@mui/material/Box'; -import Collapse from '@mui/material/Collapse'; import TextField from '@mui/material/TextField'; -import { useFormikContext } from 'formik'; +import { FieldArray, FieldArrayRenderProps, useFormikContext } from 'formik'; import { ISecurityRule } from 'hooks/api/useSecurityApi'; import { useApi } from 'hooks/useApi'; import { useEffect, useState } from 'react'; -import { TransitionGroup } from 'react-transition-group'; +import { alphabetizeObjects } from 'utils/Utils'; +import { ISecurityRuleFormProps } from './SecuritiesDialog'; import SecurityRuleCard from './SecurityRuleCard'; const SecurityRuleForm = () => { - const { handleSubmit, values, errors } = useFormikContext(); - const [selectedRules, setSelectedRules] = useState([]); + const { handleSubmit, errors, values } = useFormikContext(); const [rules, setRules] = useState([]); - const [searchText, setSearchText] = useState(''); const api = useApi(); - console.log(values); useEffect(() => { const fetchData = async () => { const data = await api.security.getActiveSecurityRules(); setRules(data); - setSelectedRules([]); }; fetchData(); @@ -33,108 +29,97 @@ const SecurityRuleForm = () => { return (
- Manage Team Members + Manage Security Rules - A minimum of one team member must be assigned the coordinator role. + A minimum of one security rule must be selected. - {errors?.['participants'] && !selectedRules.length && ( + {errors?.['rules'] && !values.rules.length && ( No Rules Selected - At least one team member needs to be added to this project. + At least one security rule needs to be selected. )} - {errors?.['participants'] && selectedRules.length > 0 && ( - {/* */} - )} - - { - // const searchFilter = createFilterOptions({ ignoreCase: true }); - // const unselectedOptions = options.filter( - // (item) => !selectedUsers.some((existing) => existing.system_user_id === item.system_user_id) - // ); - // return searchFilter(unselectedOptions, state); - // }} - getOptionLabel={(option) => option.name} - inputValue={searchText} - onInputChange={(_, value, reason) => { - if (reason === 'reset') { - setSearchText(''); - } else { - setSearchText(value); - } - }} - // onChange={(_, option) => { - // if (option) { - // } - // }} - renderInput={(params) => ( - - - - ) - }} - /> - )} - renderOption={(renderProps, renderOption) => { - return ( - - - - ); - }} - /> - - - - - {selectedRules.map((rule: any, index: number) => { - // const error = rowItemError(index); - return ( - - {/* */} - <> - - ); - })} - - - + + {(helpers: FieldArrayRenderProps) => ( + <> + + { + const searchFilter = createFilterOptions({ ignoreCase: true }); + const unselectedOptions = options.filter( + (item) => !values.rules.some((existing) => existing.security_rule_id === item.security_rule_id) + ); + return searchFilter(unselectedOptions, state); + }} + getOptionLabel={(option) => option.name} + isOptionEqualToValue={(option, value) => option.security_rule_id === value.security_rule_id} + onChange={(_, option) => { + if (option) { + helpers.push(option); + } + }} + renderInput={(params) => ( + + + + ) + }} + /> + )} + renderOption={(renderProps, renderOption) => { + return ( + + + + ); + }} + /> + + + {values.rules.map((rule: ISecurityRule, index: number) => { + return ( + + + helpers.remove(index)}> + + + + ); + })} + + + )} +
); diff --git a/app/src/utils/Utils.ts b/app/src/utils/Utils.ts index 48df5876d..4caf79f1e 100644 --- a/app/src/utils/Utils.ts +++ b/app/src/utils/Utils.ts @@ -2,6 +2,7 @@ import { DATE_FORMAT, TIME_FORMAT } from 'constants/dateTimeFormats'; import { IConfig } from 'contexts/configContext'; import { Feature, Polygon } from 'geojson'; import { LatLngBounds } from 'leaflet'; +import _ from 'lodash'; import moment from 'moment'; /** @@ -337,3 +338,14 @@ export const getTitle = (pageName?: string) => { export const pluralize = (quantity: number, word: string, singularSuffix = '', pluralSuffix = 's') => { return `${word}${quantity === 1 ? singularSuffix : pluralSuffix}`; }; + +/** + * For a given property, alphabetize an array of objects + * + * @param {T[]} data an array of objects to be alphabetize + * @param {string} property a key property to alphabetize the data array on + * @returns {any[]} Returns an alphabetized array of objects + */ +export const alphabetizeObjects = (data: T[], property: string) => { + return _.sortBy(data, property); +};