Filter modal is now working too

This commit is contained in:
Luke Berndt
2023-01-04 21:58:22 -05:00
parent 5a05ab509c
commit 271ecee5d5
12 changed files with 1156 additions and 411 deletions

View File

@@ -139,7 +139,7 @@ https://create-react-app.dev/docs/updating-to-new-releases/
## Managing MongoDB
MongoDB is used in the backend to store data. It is pretty fast, flexible and has worked well enough for me.
All of the files that MongoDB uses to store the DB are in the */data* directory, which gets mapped into the contianer.
All of the files that MongoDB uses to store the DB are in the */data* directory, which gets mapped into the container.
Mapping this directory makes sure that the data persists each time you run the mongo container.
### Working with the MongoDB Container

View File

@@ -13,6 +13,7 @@
"date-fns": "2.29.3",
"ejs": "3.1.8",
"history": "^5.3.0",
"mongodb": "^4.13.0",
"react": "^18.2.0",
"react-audio-player": "0.17.0",
"react-datepicker": "4.8.0",

View File

@@ -1,8 +1,8 @@
import React from "react";
import { Link } from 'react-router-dom';
import MediaPlayer from "./MediaPlayer";
import FilterModal from "./FilterModalContainer";
import GroupModal from "./GroupModalContainer";
import FilterModal from "./FilterModal";
import GroupModal from "./GroupModal";
import SupportModal from "./SupportModal";
import CalendarModal from "./CalendarModalContainer";
import CallInfo from "./CallInfo";
@@ -198,11 +198,11 @@ class CallPlayer extends React.Component {
}
search = search + `starred=true`;
}
/*
this.props.history.push({
pathname: this.props.history.location.pathname,
search: search
});
});*/
}
handleCalendarClose(didUpdate) {

View File

@@ -1,4 +1,7 @@
import React, { Component } from "react";
import React, { Component, useState } from "react";
import { setGroupFilter, setTalkgroupFilter, setAllFilter, setStarredFilter } from "../features/callPlayer/callPlayerSlice";
import { useGetGroupsQuery, useGetTalkgroupsQuery } from '../features/api/apiSlice'
import { useSelector, useDispatch } from 'react-redux'
import {
Modal,
Button,
@@ -12,136 +15,134 @@ import {
import "./FilterModal.css";
class FilterModal extends Component {
constructor(props) {
super(props)
this.handleInputChange = this.handleInputChange.bind(this);
this.handleTabChange = this.handleTabChange.bind(this);
this.toggleStarred = this.toggleStarred.bind(this);
this.handleDone = this.handleDone.bind(this);
this.handleClose = this.handleClose.bind(this);
function FilterModal(props) {
const shortName = useSelector((state) => state.callPlayer.shortName);
const globalFilterStarred = useSelector((state) => state.callPlayer.filterStarred);
const { data:groupsData, isSuccess:isGroupsSuccess } = useGetGroupsQuery(props.shortName);
const { data:talkgroupsData, isSuccess:isTalkgroupsSuccess } = useGetTalkgroupsQuery(props.shortName);
const [open, setOpen] = useState(false);
const [selectedTalkgroup, setSelectedTalkgroup] = useState([]);
const [selectedGroup, setSelectedGroup] = useState(false);
const [filterStarred, setFilterStarred] = useState(false);
const [activeTab, setActiveTab] = useState(0);
const dispatch = useDispatch()
this.state = {
open: false,
selectedTalkgroup: this.props.selectedTalkgroups,
selectedGroup: this.props.selectedGroup,
filterStarred: this.props.filterStarred,
activeTab: this.props.activeTab
const handleTabChange = (e, data) => setActiveTab(data.activeIndex);
const handleGroupChange = (e, { name, value }) => setSelectedGroup(value);
const handleTalkgroupChange = (e, { name, value }) => setSelectedTalkgroup(value);
const toggleStarred = () => setFilterStarred(!filterStarred);
const handleClose = () => props.onClose(false);
const onClose = props.onClose;
function handleDone(onClose) {
switch (activeTab) {
case 1:
if (selectedGroup) {
dispatch(setGroupFilter(selectedGroup));
props.onClose(true);
} else {
props.onClose(false);
}
break;
case 2:
if (selectedTalkgroup) {
dispatch(setTalkgroupFilter(selectedTalkgroup))
props.onClose(true);
} else {
props.onClose(false);
}
break;
default:
case 0:
dispatch(setAllFilter());
props.onClose(true);
break;
}
if (globalFilterStarred !== filterStarred) {
dispatch(setStarredFilter(filterStarred));
}
}
handleTabChange = (e, data) => this.setState({activeTab: data.activeIndex});
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
toggleStarred = () => this.setState(prevState => ({ filterStarred: !prevState.filterStarred }));
handleClose = () => this.props.onClose(false);
handleDone(event) {
switch(this.state.activeTab) {
case 1:
if (this.state.selectedGroup) {
this.props.callActions.setGroupFilter(this.state.selectedGroup);
this.props.onClose(true);
} else {
this.props.onClose(false);
}
break;
case 2:
if (this.state.selectedTalkgroup) {
this.props.callActions.setTalkgroupFilter(this.state.selectedTalkgroup);
this.props.onClose(true);
} else {
this.props.onClose(false);
}
break;
default:
case 0:
this.props.callActions.setAllFilter();
this.props.onClose(true);
break;
}
if (this.state.filterStarred !== this.props.filterStarred) {
this.props.callActions.setStarred(this.state.filterStarred);
this.props.onClose(true);
var obj;
let talkgroupList = [];
if (isTalkgroupsSuccess) {
for (const num in talkgroupsData["talkgroups"]) {
const talkgroup = talkgroupsData["talkgroups"][num];
obj = {
key: talkgroup.num,
value: talkgroup.num,
text: talkgroup.description
}
talkgroupList.push(obj);
}
}
let groupList = [];
if (isGroupsSuccess) {
for (const num in groupsData) {
const group = groupsData[num]
obj = {
key: group._id,
value: group._id,
text: group.groupName
}
groupList.push(obj);
}
}
}
componentDidUpdate(prevProps) {
const filterChanged = ((prevProps.selectedTalkgroups !== this.props.selectedTalkgroups) || (prevProps.selectedGroup !== this.props.selectedGroup));
if (filterChanged) {
this.setState({selectedTalkgroup: prevProps.selectedTalkgroups, selectedGroup: prevProps.selectedGroup, activeTab: prevProps.activeTab});
}
}
render() {
var obj;
var talkgroupList = [];
if (this.props.talkgroups) {
for (const num in this.props.talkgroups) {
const talkgroup = this.props.talkgroups[num];
obj = {
key: talkgroup.num,
value: talkgroup.num,
text: talkgroup.description
}
talkgroupList.push(obj);
const panes = [
{
menuItem: 'All', render: () => {
return (
<Tab.Pane attached={false}>
<Header>All Calls</Header>
<p>Dispaly all of the calls.</p>
</Tab.Pane>
)
}
}
var groupList = [];
if (this.props.groups) {
for (const num in this.props.groups) {
const group = this.props.groups[num];
obj = {
key: group._id,
value: group._id,
text: group.groupName
}
groupList.push(obj);
},
{
menuItem: 'Groups', render: () => {
return (
<Tab.Pane attached={false}>
<Dropdown placeholder='Groups' fluid selection options={groupList} value={selectedGroup} name='selectedGroup' onChange={handleGroupChange} />
</Tab.Pane>
)
}
}
const panes = [
{ menuItem: 'All', render: () =>
{ return (
<Tab.Pane attached={false}>
<Header>All Calls</Header>
<p>Dispaly all of the calls.</p>
</Tab.Pane>
)}},
{ menuItem: 'Groups', render: () =>
{ return (
<Tab.Pane attached={false}>
<Dropdown placeholder='Groups' fluid selection options={groupList} value={this.state.selectedGroup} name='selectedGroup' onChange={this.handleInputChange} />
},
{
menuItem: 'Talkgroups', render: () => {
return (
<Tab.Pane attached={false}>
<Dropdown placeholder='Talkgroups' fluid multiple selection options={talkgroupList} value={selectedTalkgroup} name='selectedTalkgroup' onChange={handleTalkgroupChange} />
</Tab.Pane>
)
}
},
]
return (
</Tab.Pane>
)} },
{ menuItem: 'Talkgroups', render: () =>
{ return (
<Tab.Pane attached={false}>
<Dropdown placeholder='Talkgroups' fluid multiple selection options={talkgroupList} value={this.state.selectedTalkgroup} name='selectedTalkgroup' onChange={this.handleInputChange} />
</Tab.Pane>
)} },
]
return (
<Modal open={props.open} onClose={handleClose} centered={false} size="tiny">
<Modal.Header>Select a Filter</Modal.Header>
<Modal.Content >
<Modal.Description>
<Tab menu={{ pointing: true }} panes={panes} defaultActiveIndex={activeTab} onTabChange={handleTabChange} />
<Divider />
<Checkbox label='Show only Starred calls' checked={filterStarred} name='filterStarred' onChange={toggleStarred} />
</Modal.Description>
</Modal.Content>
<Modal.Actions>
<Button onClick={()=> handleDone(onClose)} >
<Icon name='checkmark' /> Done
</Button>
</Modal.Actions>
</Modal>
<Modal open={this.props.open} onClose={this.handleClose} centered={false} size="tiny">
<Modal.Header>Select a Filter</Modal.Header>
<Modal.Content >
<Modal.Description>
<Tab menu={{ pointing: true }} panes={panes} defaultActiveIndex={this.props.activeTab} onTabChange={this.handleTabChange}/>
<Divider/>
<Checkbox label='Show only Starred calls' checked={this.state.filterStarred} name='filterStarred' onChange={this.toggleStarred}/>
</Modal.Description>
</Modal.Content>
<Modal.Actions>
<Button onClick={this.handleDone} >
<Icon name='checkmark' /> Done
</Button>
</Modal.Actions>
</Modal>
)
}
)
}
export default FilterModal;

View File

@@ -1,4 +1,8 @@
import React, { Component } from "react";
import React, { Component, useState } from "react";
import { useGetGroupsQuery } from '../features/api/apiSlice'
import { setGroupFilter } from "../features/callPlayer/callPlayerSlice";
import { useSelector, useDispatch } from 'react-redux'
import {
Modal,
Button,
@@ -6,8 +10,67 @@ import {
Dropdown
} from "semantic-ui-react";
import "./FilterModal.css";
import * as types from "./call-constants";
function GroupModal (props) {
const shortName = useSelector((state) => state.callPlayer.shortName);
const { data, isError, isLoading, isSuccess,error } = useGetGroupsQuery(props.shortName);
const [open, setOpen] = useState(false);
const [selectedGroup, setSelectedGroup] = useState(false);
const dispatch = useDispatch()
const onClose = props.onClose;
function handleDone(onClose) {
if (selectedGroup) {
dispatch(setGroupFilter(selectedGroup))
//dispatch({type: types.SET_GROUP_FILTER, groupId: selectedGroup })
onClose(true);
} else {
onClose(false);
}
}
let groupOptions = []
if (isSuccess) {
for (const num in data) {
const group = data[num];
var obj = {
key: group._id,
value: group._id,
text: group.groupName
}
groupOptions.push(obj);
}
}
return (
<Modal open={props.open} onClose={() => setOpen(false)} onOpen={() => setOpen(true)} size='tiny'>
<Modal.Header>Select a Group</Modal.Header>
<Modal.Content >
<Modal.Description>
<p>Choose the type of calls you want to listen to</p>
<Dropdown placeholder='Group...' fluid selection options={groupOptions} name='selectedGroup' onChange={(e, data) => setSelectedGroup(data.value)} />
</Modal.Description>
</Modal.Content>
<Modal.Actions>
<Button onClick={()=> handleDone(onClose)} >
<Icon name='headphones' /> Listen
</Button>
</Modal.Actions>
</Modal>
)
}
/*
class GroupModal extends Component {
constructor(props) {
super(props)
@@ -80,6 +143,6 @@ componentDidUpdate(prevProps) {
)
}
}
}*/
export default GroupModal;

View File

@@ -1,177 +0,0 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import {
Container,
Header,
Form,
Grid,
Rail,
Segment,
List,
Input,
Button,
Message,
Icon,
Table
} from "semantic-ui-react";
// ----------------------------------------------------
const requestMessageStyle = {
color: "red"
};
// ----------------------------------------------------
class CreateGroup extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
// Set initial state
this.state = {
groupName: "",
group: {
name: "",
items: []
},
talkgroups: (this.props.talkgroups ? this.props.talkgroups : [])
};
}
componentDidMount() {
//this.props.groupActions.fetchGroups(this.props.shortName);
this.props.talkgroupActions.fetchTalkgroups(this.props.shortName);
}
componentWillReceiveProps(nextProps) {
this.setState({ talkgroups: nextProps.talkgroups });
}
removeTalkgroup(event, index) {
const item = {
num: this.state.group.items[index].num,
description: this.state.group.items[index].description
};
const talkgroups = this.state.talkgroups.slice(0); // copy
for (var i = 0, len = talkgroups.length; i < len; i++) {
if (item.num < talkgroups[i].num) {
talkgroups.splice(i, 0, item);
break;
}
}
this.setState({
group: { items: this.state.group.items
.slice(0, index)
.concat(this.state.group.items.slice(index + 1))},
talkgroups: talkgroups
});
}
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
addTalkgroup(event, index) {
const item = {
num: this.state.talkgroups[index].num,
description: this.state.talkgroups[index].description
};
this.setState({
group: { items: this.state.group.items.concat(item) },
talkgroups: this.state.talkgroups
.slice(0, index)
.concat(this.state.talkgroups.slice(index + 1))
});
}
handleSubmit() {
var talkgroupNums = this.state.group.items.map(a => a.num);
const data = {shortName: this.props.shortName, groupName: this.state.groupName, talkgroups: JSON.stringify(talkgroupNums) }
this.props.groupActions.createGroup(data).then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({
requestMessage
});
} else {
this.props.groupActions.changeUrl("/system/" + this.props.shortName + "/2");
}
});
}
render() {
const talkgroups = this.state.talkgroups;
const group = this.state.group.items;
const railStyle = {
top: "15px",
minHeight: "200px"
};
return (
<div>
{talkgroups.length ? (
<Container text>
<Message
icon='upload'
header='Import Talkgroups'
content='Before you can create groups of Talkgroups, you need to import some talkgroups.'
/>
</Container>
) : (
<Grid container columns={2}>
<Grid.Column>
<Table>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Number</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
<Table.HeaderCell />
</Table.Row>
</Table.Header>
<Table.Body>
{talkgroups.map((talkgroup, i) => (
<Table.Row link="true" key={talkgroup.num + "-" + i}>
<Table.Cell>{talkgroup.num}</Table.Cell>
<Table.Cell>{talkgroup.description}</Table.Cell>
<Table.Cell>
<Icon
link
name="plus"
onClick={e => this.addTalkgroup(e, i)}
/>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
<Rail position="right">
<Segment style={railStyle}>
<Input action={{ onClick: this.handleSubmit, content: 'Save' }} name="groupName" onChange={this.handleInputChange} placeholder="Group Name" />
<List divided relaxed>
{group.map((talkgroup, i) => (
<List.Item key={"Group-" + i}>
<List.Icon
name="remove"
verticalAlign="middle"
link={true}
onClick={e => this.removeTalkgroup(e, i)}
/>
<List.Content>
{talkgroup.description}
</List.Content>
</List.Item>
))}
</List>
</Segment>
</Rail>
</Grid.Column>
</Grid>
)}
</div>
);
}
}
export default CreateGroup;

View File

@@ -1,25 +0,0 @@
import { connect } from "react-redux"
import * as groupActionCreators from "./group-actions"
import * as talkgroupActionCreators from "../Talkgroups/talkgroup-actions"
import { bindActionCreators } from 'redux'
import CreateGroup from "./CreateGroup"
//groups: state.group.items[props.match.params.shortName],
function mapStateToProps(state,props) {
return {
talkgroups: state.talkgroup.items[props.match.params.shortName],
shortName: props.match.params.shortName
}
}
function mapDispatchToProps(dispatch) {
return {
groupActions: bindActionCreators(groupActionCreators,dispatch),
talkgroupActions: bindActionCreators(talkgroupActionCreators, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(CreateGroup)

View File

@@ -1,77 +0,0 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { Link } from 'react-router-dom'
import {
Container,
Header,
Form,
Grid,
Segment,
Input,
Button,
Message,
Icon,
Table
} from "semantic-ui-react";
// ----------------------------------------------------
// ----------------------------------------------------
class ListGroups extends Component {
constructor(props) {
super(props);
}
//https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux
//https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter
render() {
const groups = this.props.groups;
if (groups) {
return (
<Table >
<Table.Header>
<Table.Row>
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>Talkgroup Count</Table.HeaderCell>
<Table.HeaderCell></Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{groups.map((group, i) => (
<Table.Row key={ "Group-" + i}>
<Table.Cell>{group.groupName}</Table.Cell>
<Table.Cell>{group.talkgroups.length}</Table.Cell>
<Table.Cell><Icon
name="up arrow"
link={true}
onClick={e => this.props.reorderGroup(i, i-1)}
/><Icon
name="down arrow"
link={true}
onClick={e => this.props.reorderGroup(i, i+1)}
/><Icon
name="remove"
link={true}
onClick={e => this.props.deleteGroup(group._id)}
/>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
);
} else {
return (
<div/>
);
}
}
}
export default ListGroups;

View File

@@ -0,0 +1,76 @@
import { createSlice } from '@reduxjs/toolkit'
const now = new Date();
const initialState = {
isWaiting: false,
shortName: "",
byId: {},
allIds: [],
oldestCallTime: now,
newestCallTime: now,
filterType: 0,
filterTalkgroups: [],
filterGroupId: false,
filterDate: false,
filterStarred: false,
currentCallId: false,
live: false
}
export const callPlayerSlice = createSlice({
name: 'callPlayer',
initialState,
reducers: {
setCurrentCallId: (state, action) => {
state.currentCallId = action.payload;
},
setCallTime: (state, action) => {
state.newestCallTime = action.payload;
state.oldestCallTime = action.payload;
},
setFilter: (state,action) => {
var filter={};
state.live = !action.payload.live ? state.live : action.payload.live;
state.filterStarred = !action.payload.filterStarred ? false : action.payload.filterStarred;
state.filterType = !action.payload.filterType ? 0 : action.payload.filterType;
state.filterTalkgroups = !action.payload.filterTalkgroups ? [] : action.payload.filterTalkgroups;
state.filterGroupId = !action.payload.filterGroupId ? false : action.payload.filterGroupId;
state.filterDate = !action.payload.filterDate ? 0 : action.payload.filterDate;
},
setLive: (state,action) => {
state.live = action.payload;
},
setShortName: (state, action) => {
state.shortName = action.payload;
},
setDateFilter: (state, action) => {
state.filterDate = action.payload;
},
setStarredFilter: (state, action) => {
state.filterStarred = action.payload;
},
setAllFilter: (state) => {
state.filterType = 0;
state.filterTalkgroups = [];
state.filterGroupId = 0;
},
setGroupFilter: (state, action) => {
state.filterType = 1;
state.filterTalkgroups = [];
state.filterGroupId = action.payload;
},
setTalkgroupFilter: (state, action) => {
state.filterType = 2;
state.filterTalkgroups = action.payload;
state.filterGroupId = 0;
},
},
})
// Action creators are generated for each case reducer function
export const { setCurrentCallId, setCallTime, setFilter, setLive, setShortName, setDateFilter, setStarredFilter, setAllFilter, setGroupFilter, setTalkgroupFilter } = callPlayerSlice.actions
export default callPlayerSlice.reducer

View File

@@ -6,6 +6,7 @@ import { applyMiddleware, compose, createStore } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import createRootReducer from './reducers'
import { apiSlice } from '../features/api/apiSlice'
import {callPlayerSlice} from "../features/callPlayer/callPlayerSlice"
import thunk from 'redux-thunk';
@@ -20,7 +21,7 @@ export default function configureStore(preloadedState) {
applyMiddleware(
routerMiddleware(history), // for dispatching history actions
thunk,
apiSlice.middleware
apiSlice.middleware,
// ... other middlewares ...
),
),

View File

@@ -7,12 +7,16 @@ import talkgroupReducer from "../Talkgroups/talkgroup-reducer"
import groupReducer from "../Group/group-reducer"
import callReducer from "../Call/call-reducer"
import {apiSlice} from "../features/api/apiSlice"
import callPlayerSlice from "../features/callPlayer/callPlayerSlice"
export default (history) => combineReducers({
router: connectRouter(history),
system: systemReducer,
talkgroup: talkgroupReducer,
group: groupReducer,
callPlayer: callPlayerSlice,
call: callReducer,
[apiSlice.reducerPath]: apiSlice.reducer
[apiSlice.reducerPath]: apiSlice.reducer,
})

File diff suppressed because it is too large Load Diff