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

@@ -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