First Commit

This commit is contained in:
Luke Berndt
2020-06-09 22:11:11 -04:00
parent 8f49aa463c
commit 3b0d3f290f
551 changed files with 193130 additions and 2 deletions

View File

@@ -0,0 +1,100 @@
import React, { Component } from "react";
import { Group } from '@vx/group';
import { curveStep} from '@vx/curve';
import { LinePath } from '@vx/shape';
import { scaleLinear, scaleBand, scaleTime } from '@vx/scale';
import { timeParse, timeFormat } from 'd3-time-format';
import { AxisBottom, AxisLeft } from '@vx/axis';
import { Dimmer, Loader } from "semantic-ui-react";
class CallChart extends Component {
constructor(props) {
super(props);
}
render() {
var callData = this.props.data;
if (!callData) {
return (
<Loader size='large'>Loading</Loader>
)
}
var callTotals = [];
var errorTotals = [];
// Define the graph dimensions and margins
const width = this.props.width;
const height = 500;
const margin = { top: 20, bottom: 40, left: 40, right: 20 };
// Then we'll create some bounds
const xMax = width - margin.left - margin.right;
const yMax = height - margin.top - margin.bottom;
// accessors
const x = d => d.x;
const y = d => d.y;
// Compose together the scale and accessor functions to get point functions
const dotColor = n => {
var colores_g = ["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00", "#b82e2e", "#316395", "#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707", "#651067", "#329262", "#5574a6", "#3b3eac"];
return colores_g[n % colores_g.length];
}
const compose = (scale, accessor) => (data) => scale(accessor(data));
var xScale, yScale, xPoint, yPoint;
if (callData) {
// We'll use some mock data from `@vx/mock-data` for this.
callTotals = callData.callTotals;
errorTotals = callData.errorTotals;
// scales
xScale = scaleTime({
range: [0, xMax],
domain: [callData.minDate, callData.maxDate],
tickFormat: () => (val) => formatDate(val)
});
yScale = scaleLinear({
range: [yMax, 0],
domain: [0, callData.maxValue],
});
xPoint = compose(xScale, x);
yPoint = compose(yScale, y);
}
return (
<svg width={width} height={height}>
<Group left={margin.left} top={margin.top} >
<AxisBottom top={yMax} scale={xScale} numTicks={width > 520 ? 10 : 5} />
<AxisLeft scale={yScale} />
<LinePath
data={callTotals}
xScale={xScale}
yScale={yScale}
x={x}
y={y}
strokeWidth={2}
curve={curveStep}
/>
<LinePath
data={errorTotals}
xScale={xScale}
yScale={yScale}
x={x}
y={y}
strokeWidth={2}
stroke="red"
curve={curveStep}
/>
</Group>
</svg>
);
}
}
export default CallChart;

View File

@@ -0,0 +1,82 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import {
Container,
Header,
Form,
Grid,
Segment,
Input,
Button,
Divider,
Message,
Icon
} from "semantic-ui-react";
import SystemForm from "./SystemForm";
// ----------------------------------------------------
const requestMessageStyle = {
color: "red"
};
// ----------------------------------------------------
class CreateSystem extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
state = {
requestMessage: ""
}
handleSubmit(system) {
this.props
.createSystem(system)
.then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({
requestMessage
});
} else {
this.props.changeUrl("/list-systems")
}
});
}
render() {
return (
<div>
<Container text>
<Header as="h2">Join Us!</Header>
<p>
Record the your community's radio systems and share them! All it
takes is a spare computer and a cheap SDR. The software you need,
along with an explanation of how to set it up, is available{" "}
<a href="https://github.com/robotastic/trunk-recorder">here</a>.
</p>
<p>
After you have the Trunk Recorder software up and running, come
back here and sign-up for an account so you can share your
recordings
</p>
<p> - Luke</p>
<Divider />
<p></p>
</Container>
<Container text>
<Header as="h1">Create System</Header>
<SystemForm onSubmit={this.handleSubmit} requestMessage={this.state.requestMessage} screenName={this.props.user.screenName}/>
</Container>
</div>
);
}
}
export default CreateSystem;

View File

@@ -0,0 +1,14 @@
import { connect } from "react-redux"
import * as systemActions from "./system-actions"
import CreateSystem from "./CreateSystem"
function mapStateToProps(state) {
return {
user: state.user
}
}
export default connect(
mapStateToProps,
systemActions
)(CreateSystem)

View File

@@ -0,0 +1,48 @@
import React, { Component } from "react"
import SystemForm from "./SystemForm";
import {
Container,
Header
} from "semantic-ui-react";
class EditSystem extends Component {
constructor(props) {
super(props);
this.handleSystemSubmit = this.handleSystemSubmit.bind(this);
}
state = {
requestMessage: ""
}
componentDidMount() {
const { dispatch } = this.props
this.props.fetchSystems()
}
handleSystemSubmit(system) {
this.props
.updateSystem(system)
.then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({
requestMessage
});
} else {
this.props.changeUrl("/system/"+system.shortName)
}
});
}
render() {
return(
<Container text>
<Header as="h1">Update System</Header>
<SystemForm onSubmit={this.handleSystemSubmit} system={this.props.system} isEditing={true} requestMessage={this.state.requestMessage} screenName={this.props.user.screenName}/>
</Container>
)
}
}
export default EditSystem

View File

@@ -0,0 +1,18 @@
import { connect } from "react-redux"
import EditSystem from "./EditSystem"
import * as systemActions from "./system-actions"
const mapStateToProps = (state, props) => {
return {
system: state.system.items.find((item) => item.shortName === props.match.params.shortName),
user: state.user
}
}
export default connect(
mapStateToProps,
systemActions
)(EditSystem)

View File

@@ -0,0 +1,133 @@
import React, { Component } from "react";
import { Group } from '@vx/group';
import { GlyphCircle } from '@vx/glyph';
import { scaleLinear, scaleBand, scaleTime, scaleOrdinal } from '@vx/scale';
import { timeParse, timeFormat } from 'd3-time-format';
import { AxisBottom, AxisLeft } from '@vx/axis';
import { Dimmer, Loader } from "semantic-ui-react";
import {
LegendOrdinal
} from '@vx/legend';
class ErrorChart extends Component {
constructor(props) {
super(props);
}
render() {
var errorData = this.props.data;
if (!errorData) {
return (
<Loader size='large'>Loading</Loader>
)
}
var data = [];
const parseDate = timeParse("%Y%m%d");
const format = timeFormat("%b %d");
const formatDate = (date) => format(parseDate(date));
// Define the graph dimensions and margins
const width = this.props.width;
const height = 500;
const margin = { top: 20, bottom: 40, left: 40, right: 20 };
// Then we'll create some bounds
const xMax = width - margin.left - margin.right;
const yMax = height - margin.top - margin.bottom;
// accessors
const x = d => d.x;
const y = d => d.y;
// Compose together the scale and accessor functions to get point functions
const dotColor = n => {
var colores_g = ["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00", "#b82e2e", "#316395", "#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707", "#651067", "#329262", "#5574a6", "#3b3eac"];
return colores_g[n % colores_g.length];
}
const compose = (scale, accessor) => (data) => scale(accessor(data));
var zScale = scaleOrdinal({
domain: errorData.legend,
range: ["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00", "#b82e2e", "#316395", "#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707", "#651067", "#329262", "#5574a6", "#3b3eac"]
});
var xScale, yScale, xPoint, yPoint;
if (errorData) {
// We'll use some mock data from `@vx/mock-data` for this.
data = errorData.data;
// scales
xScale = scaleTime({
range: [0, xMax],
domain: [errorData.minDate, errorData.maxDate],
tickFormat: () => (val) => formatDate(val)
});
yScale = scaleLinear({
range: [yMax, 0],
domain: [0, errorData.maxValue],
});
zScale = scaleOrdinal({
domain: errorData.legend,
range: ["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00", "#b82e2e", "#316395", "#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707", "#651067", "#329262", "#5574a6", "#3b3eac"]
});
xPoint = compose(xScale, x);
yPoint = compose(yScale, y);
}
return (
<div style={{
width: 900
}}>
<svg width={width} height={height} style={{
float: "left"
}}>
<Group left={margin.left} top={margin.top} >
<AxisBottom top={yMax} scale={xScale} numTicks={width > 520 ? 10 : 5} />
<AxisLeft scale={yScale} />
{data.map((band, i) => {
return (
<Group key={`band-${i}`}>
{band.data.map((point, j) => {
return (
<GlyphCircle
className="dot"
key={`point-${point.x}-${j}`}
fill={dotColor(i)}
left={xScale(x(point))}
top={yScale(y(point))}
size={6.0}
/>
);
})}
</Group>
);
})}
</Group>
</svg>
<div
style={{
float: "right"
}}
>
<LegendOrdinal
scale={zScale}
labelMargin="0 15px 0 0"
onMouseOver={data => event => {
console.log(
`mouse over: ${data.text}`,
`index: ${data.index}`,
);
}}
/>
</div>
</div>
);
}
}
export default ErrorChart;

View File

@@ -0,0 +1,70 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { Link } from 'react-router-dom'
import SystemCard from "./SystemCard";
import {
Container,
Divider,
Header,
Card,
Form,
Grid,
Segment,
Input,
Icon,
Button,
Message
} from "semantic-ui-react";
// ----------------------------------------------------
const requestMessageStyle = {
color: "red"
};
function SystemList(props) {
const systems = props.systems;
const listItems = numbers.map((number) =>
<li>{number}</li>
);
return (
<ul>{listItems}</ul>
);
}
// ----------------------------------------------------
class ListSystems extends Component {
constructor(props) {
super(props);
}
state = {
requestMessage: "",
}
componentDidMount() {
this.props.fetchSystems();
}
//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 systems = this.props.system.items;
return (
<div>
<Container>
<Header as="h1">Radio Systems</Header>
<Button onClick={(e) => this.props.changeUrl("/create-system")}><Icon name='plus' />Add System</Button>
<Divider />
<Card.Group itemsPerRow={4}>
{systems.map((system) =>
<SystemCard system={system} key={system.shortName} onClick={(e) => this.props.changeUrl("/system/" + system.shortName)}/>
)}
</Card.Group>
</Container>
</div>
);
}
}
export default ListSystems;

View File

@@ -0,0 +1,16 @@
import { connect } from "react-redux"
import * as systemActions from "./system-actions"
import ListSystems from "./ListSystems"
function mapStateToProps(state) {
return {
system: state.system
}
}
export default connect(
mapStateToProps,
systemActions
)(ListSystems)

View File

@@ -0,0 +1,72 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import {
Container,
Header,
Form,
Grid,
Segment,
Input,
Button,
Message,
Step,
Icon
} from "semantic-ui-react";
import SystemForm from "./SystemForm";
// ----------------------------------------------------
const requestMessageStyle = {
color: "red"
};
// ----------------------------------------------------
class PreCreateSystem extends Component {
constructor(props) {
super(props);
}
state = {
requestMessage: ""
}
render() {
return (
<div>
<Step.Group attached='top' widths={3}>
<Step>
<Icon name='wifi' />
<Step.Content>
<Step.Title>Trunk Recorder</Step.Title>
<Step.Description>Choose your shipping options</Step.Description>
</Step.Content>
</Step>
<Step active>
<Icon name='payment' />
<Step.Content>
<Step.Title>Billing</Step.Title>
<Step.Description>Enter billing information</Step.Description>
</Step.Content>
</Step>
<Step disabled>
<Icon name='info' />
<Step.Content>
<Step.Title>Confirm Order</Step.Title>
</Step.Content>
</Step>
</Step.Group>
<Segment attached>
<Image src='https://react.semantic-ui.com/images/wireframe/paragraph.png' />
</Segment>
</div>
);
}
}
export default PreCreateSystem;

View File

@@ -0,0 +1,27 @@
import React, { Component } from "react"
import CallChart from "./CallChart"
import { ParentSize } from "@vx/responsive";
class ResponsiveCallChart extends Component {
constructor(props) {
super(props);
}
render() {
return(
<ParentSize className="graph-container">
{({ width: w, height: h }) => {
return (
<CallChart
width={w}
height={h}
data={this.props.data}
/>
);
}}
</ParentSize>
)
}
}
export default ResponsiveCallChart

View File

@@ -0,0 +1,27 @@
import React, { Component } from "react"
import ErrorChart from "./ErrorChart"
import { ParentSize } from "@vx/responsive";
class ResponsiveErrorChart extends Component {
constructor(props) {
super(props);
}
render() {
return(
<ParentSize className="graph-container">
{({ width: w, height: h }) => {
return (
<ErrorChart
width={w}
height={h}
data={this.props.data}
/>
);
}}
</ParentSize>
)
}
}
export default ResponsiveErrorChart

View File

@@ -0,0 +1,684 @@
import React, {Component} from "react";
import {Link} from "react-router-dom";
import ListTalkgroups from "../Talkgroups/ListTalkgroups.js";
import MessageBox from "../components/pure/Message.js";
import ListGroups from "../Group/ListGroups.js";
import GroupModal from "../Group/GroupModalContainer.js";
import UpdatePermissionModal from "../Permission/UpdatePermissionModalContainer.js";
import AddPermissionModal from "../Permission/AddPermissionModalContainer.js";
import ErrorChart from "./ResponsiveErrorChart"
import CallChart from "./ResponsiveCallChart"
import {
Button,
Confirm,
Icon,
Form,
TextArea,
Header,
Container,
Message,
List,
Segment,
Tab,
Divider,
Label
} from "semantic-ui-react";
function planName(planType) {
switch (planType) {
case freePlanValue:
return "Free";
case proPlanValue:
return "Pro";
}
}
var backend_server =
process.env["BACKEND_SERVER"] != null
? process.env["BACKEND_SERVER"]
: "https://api.openmhz.com";
class System extends Component {
constructor(props) {
super(props);
this.handleUpload = this.handleUpload.bind(this);
this.reorderGroup = this.reorderGroup.bind(this);
this.deleteGroup = this.deleteGroup.bind(this);
this.editGroup = this.editGroup.bind(this);
this.saveGroupOrder = this.saveGroupOrder.bind(this);
this.showSystemDeleteConfirm = this.showSystemDeleteConfirm.bind(this);
this.showPermissionDeleteConfirm = this.showPermissionDeleteConfirm.bind(
this
);
this.updatePermission = this.updatePermission.bind(this);
this.handlePermissionDeleteConfirm = this.handlePermissionDeleteConfirm.bind(
this
);
this.handlePermissionDeleteCancel = this.handlePermissionDeleteCancel.bind(
this
);
this.handleSystemDeleteConfirm = this.handleSystemDeleteConfirm.bind(this);
this.handleSystemDeleteCancel = this.handleSystemDeleteCancel.bind(this);
this.handleUpgradePlan = this.handleUpgradePlan.bind(this);
this.handleUpdatePermissionClose = this.handleUpdatePermissionClose.bind(
this
);
this.handleGroupToggle = this.handleGroupToggle.bind(this);
this.handleGroupClose = this.handleGroupClose.bind(this);
this.handleAddPermissionToggle = this.handleAddPermissionToggle.bind(this);
this.handleAddPermissionClose = this.handleAddPermissionClose.bind(this);
this.processErrors = this.processErrors.bind(this);
this.processStatistics = this.processStatistics.bind(this);
this.state = {
openSystemDeleteConfirm: false,
openPermissionDeleteConfirm: false,
openMessage: false,
requestMessage: "",
groupVisible: false,
editGroupId: false,
selectedPermission: false,
addPermissionVisible: false,
updatePermissionVisible: false,
updatePermissionId: false
};
}
processErrors(errors) {
var allData = [];
var num = 0;
const now = new Date();
var maxDate = now;
var minDate = now;
var minValue = 0;
var maxValue = 0;
var legend = [];
for (var j = 0; j < errors.length; j++) {
var data = [];
for (var i = 0; i < errors[j].values.length; i++) {
var time = new Date(errors[j].values[i].time);
if (errors[j].values[i].errors > maxValue)
maxValue = errors[j].values[i].errors;
if (time < minDate) minDate = time;
var value = {
id: num++,
y: errors[j].values[i].errors,
x: time
};
data.push(value);
}
const id = Math.floor(errors[j]._id / 1000) / 1000 + "MHz";
legend.push(id);
var obj = {
id: id,
data: data
};
allData.push(obj);
}
const errorData = {
data: allData,
minDate: minDate,
maxDate: maxDate,
minValue: minValue,
maxValue: maxValue,
legend: legend
};
this.setState({ errorData: errorData });
}
processStatistics(statistic) {
var callTotals = [];
var errorTotals = [];
const now = new Date();
var maxDate = now;
var minDate = now;
var minValue = 0;
var maxValue = 0;
var MS_PER_MINUTE = 60000;
for (var j = 0; j < statistic.callTotals.length; j++) {
var spotsBack = statistic.callTotals.length - j;
var time = new Date(now - spotsBack * 15 * MS_PER_MINUTE);
if (time < minDate) minDate = time;
if (statistic.callTotals[j] > maxValue)
maxValue = statistic.callTotals[j];
callTotals.push({ y: statistic.callTotals[j], x: time });
}
for (var j = 0; j < statistic.errorTotals.length; j++) {
var spotsBack = statistic.errorTotals.length - j;
var time = new Date(now - spotsBack * 15 * MS_PER_MINUTE);
if (time < minDate) minDate = time;
if (statistic.errorTotals[j] > maxValue)
maxValue = statistic.errorTotals[j];
errorTotals.push({ x: time, y: statistic.errorTotals[j] });
}
var callData = {
minDate: minDate,
maxDate: maxDate,
minValue: minValue,
maxValue: maxValue,
callTotals: callTotals,
errorTotals: errorTotals
};
this.setState({ callData: callData });
}
componentDidMount() {
this.props.systemActions.fetchSystems();
this.props.talkgroupActions.fetchTalkgroups(this.props.shortName);
this.props.groupActions.fetchGroups(this.props.shortName);
this.props.errorActions.fetch(this.props.shortName);
this.props.permissionActions.fetch(this.props.shortName);
if (this.props.statistic) {
this.processStatistics(this.props.statistic);
}
if (this.props.errors) {
this.processErrors(this.props.errors);
}
}
componentWillReceiveProps(nextProp) {
if (!this.props.errors && nextProp.errors) {
this.processErrors(nextProp.errors);
}
if (!this.props.statistic && nextProp.statistic) {
this.processStatistics(nextProp.statistic);
}
}
updatePermission(permissionId) {
this.setState({
updatePermissionId: permissionId,
updatePermissionVisible: !this.state.updatePermissionVisible
});
}
editGroup(groupId) {
this.setState({
editGroupId: groupId,
groupVisible: !this.state.groupVisible
});
}
deleteGroup(groupId) {
this.props.groupActions.deleteGroup(this.props.shortName, groupId);
}
reorderGroup(oldIndex, newIndex) {
this.props.groupActions.reorderGroup(
this.props.shortName,
oldIndex,
newIndex
);
}
handleUpdatePermissionClose(didUpdate) {
this.setState({
updatePermissionVisible: !this.state.updatePermissionVisible,
updatePermissionId: false
});
}
handleAddPermissionClose(didUpdate) {
this.setState({
addPermissionVisible: !this.state.addPermissionVisible
});
}
handleAddPermissionToggle = () =>
this.setState({
addPermissionVisible: !this.state.addPermissionVisible
});
handleGroupClose(didUpdate) {
this.setState({
groupVisible: !this.state.groupVisible,
editGroupId: false
});
}
handleGroupToggle = () => this.setState({
editGroupId: false,
groupVisible: !this.state.groupVisible
})
handleExport() {
this.props.talkgroupActions
.exportTalkgroups(this.props.system.shortName)
.then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({ requestMessage });
}
});
}
saveGroupOrder() {
const order = this.props.groups.map(x => x._id);
const data = {
groupOrder: JSON.stringify(order)
};
this.props.groupActions
.saveGroupOrder(this.props.system.shortName, data)
.then(requestMessage => {
if (requestMessage) {
}
});
}
handleUpgradePlan() {
window.location = account_server + "/update-plans";
}
handleUpload(file) {
this.props.talkgroupActions
.importTalkgroups(this.props.system.shortName, file)
.then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({ requestMessage });
}
});
}
// https://github.com/Semantic-Org/Semantic-UI-React/issues/2103
showPermissionDeleteConfirm = permission =>
this.setState({
openPermissionDeleteConfirm: true,
selectedPermission: permission
});
showSystemDeleteConfirm = () =>
this.setState({ openSystemDeleteConfirm: true });
handlePermissionDeleteConfirm = permissionId => {
this.setState({ openPermissionDeleteConfirm: false });
this.props.permissionActions
.deletePermission({
shortName: this.props.system.shortName,
permissionId: this.state.selectedPermission._id
})
.then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during login
this.setState({ requestMessage: requestMessage, openMessage: true });
}
});
};
handleSystemDeleteConfirm = () => {
this.setState({ openSystemDeleteConfirm: false });
this.props.systemActions
.deleteSystem(this.props.system.shortName)
.then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during login
this.setState({ requestMessage: requestMessage, openMessage: true });
}
});
};
handleSystemDeleteCancel = () =>
this.setState({ openSystemDeleteConfirm: false });
handlePermissionDeleteCancel = () =>
this.setState({ openPermissionDeleteConfirm: false });
render() {
const system = this.props.system;
let fileInput = null;
var location = "";
var example = "";
var plan = "";
var groupRender = () => {};
if (system) {
if (this.props.talkgroups && this.props.talkgroups.length) {
groupRender = () => (
<div>
<Button onClick={this.handleGroupToggle}>Create Group</Button>
<Button onClick={this.saveGroupOrder} disabled={!this.props.orderChange}> Save Order</Button>
<ListGroups
groups={this.props.groups}
reorderGroup={this.reorderGroup}
deleteGroup={this.deleteGroup}
editGroup={this.editGroup}
/>
</div>
);
} else {
groupRender = () => (
<Container text>
<Message
icon="upload"
header="Import Talkgroups"
content="Before you can create groups of Talkgroups, you need to import some talkgroups."
/>
</Container>
);
}
switch (system.planType) {
case freePlanValue:
const tempPlan = planName(system.planType) + " plan";
plan = (
<div>
{tempPlan}
<Label as="a" onClick={this.handleUpgradePlan}>
{" "}
<Icon name="plus" />
Upgrade Plan
</Label>
</div>
);
break;
case proPlanValue:
plan = planName(system.planType) + " plan";
break;
}
example =
'{\n\t"systems": [\n\t\t{\n\t\t\t"shortName": "' +
system.shortName +
'",\n\t\t\t"apiKey": "' +
system.key +
'"\n\t\t}\n\t],\n\t"uploadServer": "' +
backend_server +
'"\n}';
switch (system.systemType) {
case "state":
location = system.state;
break;
case "city":
location = system.city + ", " + system.state;
break;
case "county":
location = system.county + ", " + system.state;
break;
case "international":
location = system.country;
break;
}
}
const panes = [
{
menuItem: "Overview",
render: () => (
<div>
<Container text>
<Segment.Group>
<Segment>
<Header size="small">Location</Header>
{location}
</Segment>
<Segment>
<Header size="small">Short Name</Header>
{this.props.system.shortName}
</Segment>
<Segment>
<Header size="small">Description</Header>
{this.props.system.description}
</Segment>
<Segment clearing={true}>
<Button
color="red"
onClick={this.showSystemDeleteConfirm}
disabled={this.props.systemisWaiting}
content="Delete"
floated="right"
/>
<Button
as={Link}
to={"/update-system/" + this.props.system.shortName}
content="Update"
disabled={this.props.systemisWaiting}
floated="left"
/>
</Segment>
</Segment.Group>
</Container>
</div>
)
},
{
menuItem: "Config",
render: () => (
<div>
<Container text>
<Header as="h2">Configure Trunk Recorder</Header>
<p>
Below are the parameters you will need to configure Trunk
Recorder. The example below shows how they should be added to
the config.json file.
</p>
<Segment.Group>
<Segment>
<Header size="small">API Key</Header>
{this.props.system.key}
</Segment>
<Segment>
<Header size="small">Short Name</Header>
{this.props.system.shortName}
</Segment>
<Segment>
<Header size="small">Upload Server</Header>
{backend_server}
</Segment>
<Segment>
<Header size="small">Example</Header>
<Form>
<TextArea rows="10" readOnly="readOnly" value={example} />
</Form>
</Segment>
</Segment.Group>
</Container>
</div>
)
},
{
menuItem: "Talkgroups",
render: () => (
<div>
<Container text>
<p>
<em>
This is the same .csv talkgroup file from Trunk Recorder...
except the Hex column has been deleted
</em>
</p>
<p>The columns for the file are:</p>
<List ordered celled>
<List.Item>
<List.Content>
<List.Header>Number</List.Header>
<List.Description>The talkgroup number</List.Description>
</List.Content>
</List.Item>
<List.Item>
<List.Content>
<List.Header>Mode</List.Header>
<List.Description>
The mode for the talkgroup, it can be: 'A' = analog, 'D' =
P25 Digital, 'E' = Encrypted
</List.Description>
</List.Content>
</List.Item>
<List.Item>
<List.Content>
<List.Header>Alpha</List.Header>
<List.Description>
Up to 12 charecter description of the talkgroup
</List.Description>
</List.Content>
</List.Item>
<List.Item>
<List.Content>
<List.Header>Description</List.Header>
<List.Description>
The full title for the talkgroup
</List.Description>
</List.Content>
</List.Item>
<List.Item>
<List.Content>
<List.Header>Tag</List.Header>
<List.Description>
The type of talkgroup, eg: Dispatch, Fireground
</List.Description>
</List.Content>
</List.Item>
<List.Item>
<List.Content>
<List.Header>Group</List.Header>
<List.Description>
The department that uses this talkgroup
</List.Description>
</List.Content>
</List.Item>
<List.Item>
<List.Content>
<List.Header>Priority</List.Header>
<List.Description>
The priority for recording this talkgroup, 100 or over
will be skipped
</List.Description>
</List.Content>
</List.Item>
</List>
</Container>
<Divider />
<span>
<label htmlFor="upload-button" className="ui icon button">
<i className="upload icon" />
Import Talkgroups
</label>
<input
type="file"
id="upload-button"
style={{
display: "none"
}}
onChange={() => {
this.handleUpload(fileInput.files[0]);
}}
ref={input => {
fileInput = input;
}}
/>
</span>
<a href={"/talkgroups/" + this.props.system.shortName + "/export"}>
<Button>
<i className="upload icon" />
Export Talkgroups
</Button>
</a>
<ListTalkgroups talkgroups={this.props.talkgroups} />
</div>
)
},
{
menuItem: "Groups",
render: groupRender
},
/*{
menuItem: "Users",
render: () => (<Tab.Pane>
<Button onClick={this.handleAddPermissionToggle} >
Add User
</Button>
<ListPermissions permissions={this.props.permissions} updatePermission={this.updatePermission} deletePermission={this.showPermissionDeleteConfirm}/>
</Tab.Pane>)
},*/ {
menuItem: "Stats",
render: () => (
<div>
<Divider horizontal>
<Icon name="microphone" />
24-Hour Call History
</Divider>
<CallChart data={this.state.callData} />
<Divider horizontal>
<Icon name="microphone" />
Hourly Vocoder Error Percentage
</Divider>
<ErrorChart data={this.state.errorData} />
</div>
)
}
];
return (
<div>
<AddPermissionModal
shortName={this.props.shortName}
open={this.state.addPermissionVisible}
onClose={this.handleAddPermissionClose}
/>
<GroupModal
shortName={this.props.shortName}
editGroupId={this.state.editGroupId}
open={this.state.groupVisible}
onClose={this.handleGroupClose}
/>
<UpdatePermissionModal
shortName={this.props.shortName}
updatePermissionId={this.state.updatePermissionId}
open={this.state.updatePermissionVisible}
onClose={this.handleUpdatePermissionClose}
/>
{system ? (
<Container>
<MessageBox
open={this.state.openMessage}
message={this.state.requestMessage}
title="Delete Failed"
onClose={() => this.setState({ openMessage: false })}
/>
<Confirm
open={this.state.openSystemDeleteConfirm}
header="Are you sure you want to?"
content={"Delete system - " + this.props.system.shortName}
onCancel={this.handleSystemDeleteCancel}
onConfirm={this.handleSystemDeleteConfirm}
/>
<Confirm
open={this.state.openPermissionDeleteConfirm}
header="Are you sure you want to?"
content={
"Remove access for: " + this.state.selectedPermission.email
}
onCancel={this.handlePermissionDeleteCancel}
onConfirm={this.handlePermissionDeleteConfirm}
/>
<Header as="h1">
<Header.Content>
{this.props.system.name}
{/* <Header.Subheader>{plan}</Header.Subheader> */}
</Header.Content>
</Header>
<Tab
menu={{
secondary: true,
pointing: true
}}
panes={panes}
defaultActiveIndex={this.props.activeTab}
/>
</Container>
) : (
<div />
)}
</div>
);
}
}
export default System;

View File

@@ -0,0 +1,74 @@
import React, { Component } from "react";
import {
Card,
Icon,
Label,
Header
} from "semantic-ui-react";
function planName(planType) {
switch(planType) {
case freePlanValue:
return "Free";
case proPlanValue:
return "Pro";
}
}
class SystemCard extends Component {
constructor(props) {
super(props);
}
render() {
var location = "";
const system = this.props.system;
var plan=null;
if (system) {
if (system.planType) {
plan = planName(system.planType);
}
switch (system.systemType) {
case "state":
location = system.state;
break;
case "city":
location = system.city + ", " + system.state;
break;
case "county":
location = system.county + ", " + system.state;
break;
case "international":
location = system.country;
break;
}
}
return (
<Card link onClick={this.props.onClick}>
<Card.Content>
<Header size='medium'>
<Header.Content>
{system.name}
<Header.Subheader>{system.screenName}</Header.Subheader>
</Header.Content>
</Header>{plan&&<Label color='orange' ribbon='right'>{plan}</Label>}
</Card.Content><Card.Content>
<Card.Description>{system.description}</Card.Description>
</Card.Content>
<Card.Content extra>
<Icon name='marker' />
{location}
</Card.Content>
</Card>
);
}
}
export default SystemCard;

View File

@@ -0,0 +1,40 @@
import { connect } from "react-redux"
import * as systemActionCreators from "./system-actions"
import * as talkgroupActionCreators from "../Talkgroups/talkgroup-actions"
import * as groupActionCreators from "../Group/group-actions"
import * as permissionActionCreators from "../Permission/permission-actions"
import * as errorActionCreators from "./error-actions"
import { bindActionCreators } from 'redux'
import System from "./System"
function mapStateToProps(state,props) {
return {
system: state.system.items.find((item) => item.shortName === props.match.params.shortName),
permissions: state.permission.items,
statistic: state.statistic[props.match.params.shortName],
systemIsWaiting: state.system.isWaiting,
talkgroupsIsWaiting: state.talkgroup.isWaiting,
groupsIsWaiting: state.group.isWaiting,
talkgroups: state.talkgroup.items[props.match.params.shortName],
orderChange: state.group.orderChange,
groups: state.group.items[props.match.params.shortName],
errors: state.error.items[props.match.params.shortName],
shortName: props.match.params.shortName,
activeTab: (props.match.params.tab ? props.match.params.tab : 0)
}
}
function mapDispatchToProps(dispatch) {
return {
groupActions: bindActionCreators(groupActionCreators, dispatch),
systemActions: bindActionCreators(systemActionCreators, dispatch),
talkgroupActions: bindActionCreators(talkgroupActionCreators, dispatch),
errorActions: bindActionCreators(errorActionCreators, dispatch),
permissionActions: bindActionCreators(permissionActionCreators, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(System)

View File

@@ -0,0 +1,428 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import {
Container,
Header,
Dropdown,
Divider,
TextArea,
Form,
Grid,
Segment,
Input,
Button,
Message,
Checkbox,
Icon
} from "semantic-ui-react";
// ----------------------------------------------------
const checkInputMessagestyle = {
color: "red"
};
// ----------------------------------------------------
class SystemForm extends Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.checkInputs = this.checkInputs.bind(this);
this.render = this.render.bind(this);
this.state = {
checkInputMessages: [],
name: "",
shortName: "",
description: "",
systemType: "state",
state: "",
city: "",
county: "",
country: "",
showScreenName: false,
nameError: false,
shortNameError: false,
descriptionError: false,
emailError: false,
systemTypeError: false,
stateError: false,
cityErorr: false,
countyError: false,
countryError: false,
isEditing: this.props.isEditing
};
if (this.props.isEditing && this.props.system)
{
this.state.name = this.props.system.name;
this.state.shortName = this.props.system.shortName;
this.state.description = this.props.system.description;
this.state.systemType = this.props.system.systemType;
this.state.state = this.props.system.state;
this.state.county = this.props.system.county;
this.state.country = this.props.system.country;
this.state.showScreenName = this.props.system.showScreenName;
}
}
componentWillReceiveProps(nextProps) {
if (this.props.isEditing && nextProps.system) {
this.setState({
name: nextProps.system.name,
shortName: nextProps.system.shortName,
description: nextProps.system.description,
systemType: nextProps.system.systemType,
state: nextProps.system.state,
county: nextProps.system.county,
country: nextProps.system.country,
showScreenName: nextProps.system.showScreenName,
});
}
}
checkInputs() {
let error = false;
var checkInputMessages = [];
if (this.state.name === "") {
this.setState({ nameError: true });
checkInputMessages.push("System Name is required");
error = true;
} else {
this.setState({ nameError: false });
}
if (this.state.shortName.length == 0 || this.state.shortName.length > 10) {
this.setState({ shortNameError: true });
checkInputMessages.push(
"Short Name for the system has to be 4-10 charecters"
);
error = true;
} else {
this.setState({ shortNameError: false });
}
if (this.state.description === "") {
this.setState({ descriptionError: true });
checkInputMessages.push("System description is required");
error = true;
} else {
this.setState({ descriptionError: false });
}
if (
this.state.systemType === "state" ||
this.state.systemType === "city" ||
this.state.systemType === "county"
) {
if (this.state.state === "") {
this.setState({ stateError: true });
checkInputMessages.push("Select a state");
error = true;
} else {
this.setState({ stateError: false });
}
}
if (this.state.systemType === "city") {
if (this.state.city === "") {
this.setState({ cityError: true });
checkInputMessages.push("Enter a city");
error = true;
} else {
this.setState({ cityError: false });
}
}
if (this.state.systemType === "county") {
if (this.state.county === "") {
this.setState({ countyError: true });
checkInputMessages.push("Enter a city");
error = true;
} else {
this.setState({ countyError: false });
}
}
if (this.state.systemType === "international") {
if (this.state.country === "") {
this.setState({ countryError: true });
checkInputMessages.push("Enter a city");
error = true;
} else {
this.setState({ countryError: false });
}
}
this.setState({ checkInputMessages: checkInputMessages });
return error;
}
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
handleCheckboxChange() {
this.setState({showScreenName: !this.state.showScreenName});
}
handleSubmit(event) {
event.preventDefault();
let inputError = this.checkInputs();
if (!inputError) {
const system = (({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
showScreenName
}) => ({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
showScreenName
}))(this.state);
this.props.onSubmit(system);
}
}
render() {
var requestMessage = "";
var floatStyle = {
overflow: "auto"
};
const { isEditing } = this.props;
const stateOptions = [
{ value: "AL", text: "Alabama" },
{ value: "AK", text: "Alaska" },
{ value: "AZ", text: "Arizona" },
{ value: "AR", text: "Arkansas" },
{ value: "CA", text: "California" },
{ value: "CO", text: "Colorado" },
{ value: "CT", text: "Connecticut" },
{ value: "DE", text: "Delaware" },
{ value: "DC", text: "District Of Columbia" },
{ value: "FL", text: "Florida" },
{ value: "GA", text: "Georgia" },
{ value: "HI", text: "Hawaii" },
{ value: "ID", text: "Idaho" },
{ value: "IL", text: "Illinois" },
{ value: "IN", text: "Indiana" },
{ value: "IA", text: "Iowa" },
{ value: "KS", text: "Kansas" },
{ value: "KY", text: "Kentucky" },
{ value: "LA", text: "Louisiana" },
{ value: "ME", text: "Maine" },
{ value: "MD", text: "Maryland" },
{ value: "MA", text: "Massachusetts" },
{ value: "MI", text: "Michigan" },
{ value: "MN", text: "Minnesota" },
{ value: "MS", text: "Mississippi" },
{ value: "MO", text: "Missouri" },
{ value: "MT", text: "Montana" },
{ value: "NE", text: "Nebraska" },
{ value: "NV", text: "Nevada" },
{ value: "NH", text: "New Hampshire" },
{ value: "NJ", text: "New Jersey" },
{ value: "NM", text: "New Mexico" },
{ value: "NY", text: "New York" },
{ value: "NC", text: "North Carolina" },
{ value: "ND", text: "North Dakota" },
{ value: "OH", text: "Ohio" },
{ value: "OK", text: "Oklahoma" },
{ value: "OR", text: "Oregon" },
{ value: "PA", text: "Pennsylvania" },
{ value: "RI", text: "Rhode Island" },
{ value: "SC", text: "South Carolina" },
{ value: "SD", text: "South Dakota" },
{ value: "TN", text: "Tennessee" },
{ value: "TX", text: "Texas" },
{ value: "UT", text: "Utah" },
{ value: "VT", text: "Vermont" },
{ value: "VA", text: "Virginia" },
{ value: "WA", text: "Washington" },
{ value: "WV", text: "West Virginia" },
{ value: "WI", text: "Wisconsin" },
{ value: "WY", text: "Wyoming" }
];
if (
this.state.checkInputMessages.length ||
this.props.requestMessage.length
) {
var counter = 0;
requestMessage = (
<Message compact warning icon>
<Icon name="lightning" />
<Message.Content>
<Message.Header>Problems...</Message.Header>
<ul>
{this.state.checkInputMessages.map(function(listValue) {
return <li key={`ListItem_${counter++}`}>{listValue}</li>;
})}
{this.props.requestMessage.length > 0 && (
<li key="req-mess">{this.props.requestMessage}</li>
)}
</ul>
</Message.Content>
</Message>
);
}
return (
<div>
<Form className="raised padding segment" onSubmit={this.handleSubmit}>
<Form.Group widths="equal">
<Form.Field>
<Form.Input
type="text"
name="name"
onChange={this.handleInputChange}
error={this.state.nameError}
value={this.state.name}
label="System name"
placeholder="System Name..."
/>
</Form.Field>
<Form.Field>
<Form.Input
type="text"
name="shortName"
onChange={this.handleInputChange}
error={this.state.shortNameError}
value={this.state.shortName}
disabled={isEditing}
label="System Short Name (4-10 characters)"
placeholder="Short Name..."
/>
</Form.Field>
</Form.Group>
<Divider horizontal />
<Form.Field>
<p>Your Screen Name: {this.props.screenName}</p>
<Checkbox
onChange={this.handleCheckboxChange}
checked={this.state.showScreenName}
label="Show in System Listing" />
</Form.Field>
<Divider horizontal />
<Form.TextArea
label="Description"
name="description"
onChange={this.handleInputChange}
error={this.state.descriptionError}
value={this.state.description}
rows={2}
placeholder="Description of system..."
/>
<Divider horizontal><Icon name="marker" />Location</Divider>
<Form.Group inline>
<label>System Type</label>
<Form.Radio
label="State"
name="systemType"
value="state"
checked={this.state.systemType === "state"}
onChange={this.handleInputChange}
/>
<Form.Radio
label="County"
name="systemType"
value="county"
checked={this.state.systemType === "county"}
onChange={this.handleInputChange}
/>
<Form.Radio
label="City"
name="systemType"
value="city"
checked={this.state.systemType === "city"}
onChange={this.handleInputChange}
/>
<Form.Radio
label="International"
name="systemType"
value="international"
checked={this.state.systemType === "international"}
onChange={this.handleInputChange}
/>
</Form.Group>
<Form.Group widths="equal">
{this.state.systemType === "city" && (
<Form.Field>
<Form.Input
type="text"
name="city"
onChange={this.handleInputChange}
error={this.state.cityError}
value={this.state.city}
label="City"
placeholder="City..."
/>
</Form.Field>
)}
{this.state.systemType === "county" && (
<Form.Field>
<Form.Input
type="text"
name="county"
onChange={this.handleInputChange}
error={this.state.countyError}
value={this.state.county}
label="County"
placeholder="County..."
/>
</Form.Field>
)}
{this.state.systemType === "international" ? (
<Form.Field>
<Form.Input
type="text"
name="country"
onChange={this.handleInputChange}
error={this.state.countryError}
value={this.state.country}
label="Country"
placeholder="Country..."
/>
</Form.Field>
) : (
<Form.Field>
<Form.Dropdown
name="state"
onChange={this.handleInputChange}
error={this.state.stateError}
value={this.state.state}
options={stateOptions}
label="State"
placeholder="Select State..."
/>
</Form.Field>
)}
</Form.Group>
<div style={floatStyle}>
<Button type="submit" size="large" floated="right" value="Login">
{isEditing != true ? "Create" : "Update"}
</Button>
</div>
</Form>
{requestMessage}
</div>
);
}
}
export default SystemForm;

View File

@@ -0,0 +1,63 @@
import axios from "axios";
//import { browserHistory } from "react-router"
import * as types from "./error-constants";
import { push } from 'connected-react-router';
function beginFetch() {
return { type: types.FETCH };
}
function fetchSuccess(data) {
return { type: types.FETCH_SUCCESS,
data };
}
function fetchError(data) {
return { type: types.FETCH_ERROR,
data };
}
function makeUserRequest(method, data, api) {
// returns a Promise
return axios({
method: method,
url: api,
data: data
});
}
export function changeUrl(url) {
return dispatch => {
dispatch(push(url));
};
}
export function fetch(shortName) {
return (dispatch, getState) => {
dispatch(beginFetch());
return axios
.get(backend_server + "/" + shortName + "/errors")
.then(response => {
if (response.data) {
var data ={
shortName: shortName,
errors: response.data
}
dispatch(fetchSuccess(data));
} else {
dispatch(fetchError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}

View File

@@ -0,0 +1,3 @@
export const FETCH = "FETCH"
export const FETCH_SUCCESS = "FETCH_SUCCESS"
export const FETCH_ERROR = "FETCH_ERROR"

View File

@@ -0,0 +1,42 @@
import {
FETCH,
FETCH_SUCCESS,
FETCH_ERROR
} from "./error-constants";
const error = (
state = {
isWaiting: false,
items: {}
},
action
) => {
switch (action.type) {
case FETCH:
return Object.assign({}, state, { isWaiting: true});
case FETCH_SUCCESS:
return Object.assign({}, state, {
isWaiting: false,
items: {
...state.items,
[action.data.shortName]: action.data.errors
}
});
case FETCH_ERROR:
return Object.assign({}, state, {
isWaiting: false
});
default:
return state;
}
};
export default error;

View File

@@ -0,0 +1,20 @@
const FETCH_SUCCESS_STATISTIC = "FETCH_SUCCESS_STATISTIC"
const statistic = (
state = {},
action
) => {
switch (action.type) {
case FETCH_SUCCESS_STATISTIC:
return Object.assign({}, state, {...action.data});
default:
return state;
}
};
export default statistic;

View File

@@ -0,0 +1,233 @@
import axios from "axios";
//import { browserHistory } from "react-router"
import * as types from "./system-constants";
import { push } from 'connected-react-router';
// "Log In" action creators
function beginCreateSystem() {
return { type: types.CREATE_SYSTEM };
}
function createSystemSuccess(data) {
return {
type: types.CREATE_SUCCESS_SYSTEM,
data
};
}
function createSystemError(data) {
return {
type: types.CREATE_ERROR_SYSTEM,
data
};
}
function beginUpdateSystem() {
return { type: types.UPDATE_SYSTEM };
}
function updateSystemSuccess(data) {
return { type: types.UPDATE_SUCCESS_SYSTEM, data };
}
function updateSystemError(data) {
return { type: types.UPDATE_ERROR_SYSTEM, data };
}
function beginDeleteSystem() {
return { type: types.DELETE_SYSTEM };
}
function deleteSystemSuccess(data) {
return { type: types.DELETE_SUCCESS_SYSTEM,
data };
}
function deleteSystemError() {
return { type: types.DELETE_ERROR_SYSTEM };
}
function beginFetchSystem() {
return { type: types.FETCH_SYSTEM };
}
function fetchSystemSuccess(data) {
return { type: types.FETCH_SUCCESS_SYSTEM,
data };
}
function fetchStatisticSuccess(data) {
return { type: types.FETCH_SUCCESS_STATISTIC,
data };
}
function fetchSystemError(data) {
return { type: types.FETCH_ERROR_SYSTEM,
data };
}
function beginFetchErrors() {
return { type: types.FETCH_ERROR };
}
function fetchErrorsSuccess(data) {
return { type: types.FETCH_SUCCESS_ERROR,
data };
}
function fetchErrorsError(data) {
return { type: types.FETCH_ERROR_ERROR,
data };
}
function makeUserRequest(method, data, api = "/login") {
// returns a Promise
return axios({
method: method,
url: api,
data: data
});
}
export function changeUrl(url) {
return dispatch => {
dispatch(push(url));
};
}
// Example of an Async Action Creator
// http://redux.js.org/docs/advanced/AsyncActions.html
export function updateSystem(data) {
return dispatch => {
dispatch(beginUpdateSystem());
return makeUserRequest("post", data, "/systems/" + data.shortName)
.then(response => {
if (response.data.success) {
const user = response.data.user
dispatch(updateSystemSuccess(response.data.system));
} else {
dispatch(updateSystemError());
return response.data.message;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}
function shouldFetchSystems(state, subreddit) {
if ((!state.system.items) || (state.system.items.length <1)) {
return true
} else if (state.system.isWaiting) {
return false
} else {
return false
}
}
export function fetchErrors(shortName) {
return (dispatch, getState) => {
dispatch(beginFetchError());
return axios
.get(backend_server + "/" + shortName + "/systems")
.then(response => {
if (response.data) {
dispatch(fetchErrorsSuccess(shortName, response.data));
} else {
dispatch(fetchErrorsError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}
export function fetchSystems() {
return (dispatch, getState) => {
if (shouldFetchSystems(getState())) {
dispatch(beginFetchSystem());
return axios
.get("/systems")
.then(response => {
if (response.data.success) {
dispatch(fetchSystemSuccess(response.data.systems));
dispatch(fetchStatisticSuccess(response.data.stats));
} else {
dispatch(fetchSystemError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
};
}
export function deleteSystem(shortName) {
return dispatch => {
dispatch(beginDeleteSystem());
return makeUserRequest("delete", null, "/systems/"+shortName)
.then(response => {
if (response.data.success) {
dispatch(deleteSystemSuccess({shortName: shortName}));
dispatch(push("/list-systems"));
} else {
dispatch(deleteSystemError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}
export function createSystem(data) {
return dispatch => {
dispatch(beginCreateSystem());
return makeUserRequest("post", data, "/systems")
.then(response => {
if (response.data.success) {
dispatch(createSystemSuccess(response.data.system));
} else {
dispatch(createSystemError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}

View File

@@ -0,0 +1,16 @@
export const CREATE_SYSTEM = "CREATE_SYSTEM"
export const CREATE_SUCCESS_SYSTEM = "CREATE_SUCCESS_SYSTEM"
export const CREATE_ERROR_SYSTEM = "CREATE_ERROR_SYSTEM"
export const UPDATE_SYSTEM = "UPDATE_SYSTEM"
export const UPDATE_SUCCESS_SYSTEM = "UPDATE_SUCCESS_SYSTEM"
export const UPDATE_ERROR_SYSTEM = "UPDATE_ERROR_SYSTEM"
export const DELETE_SYSTEM = "DELETE_SYSTEM"
export const DELETE_SUCCESS_SYSTEM = "DELETE_SUCCESS_SYSTEM"
export const DELETE_ERROR_SYSTEM = "DELETE_ERROR_SYSTEM"
export const FETCH_SYSTEM = "FETCH_SYSTEM"
export const FETCH_SUCCESS_SYSTEM = "FETCH_SUCCESS_SYSTEM"
export const FETCH_ERROR_SYSTEM = "FETCH_ERROR_SYSTEM"
export const FETCH_SUCCESS_STATISTIC = "FETCH_SUCCESS_STATISTIC"
export const FETCH_ERROR = "FETCH_ERROR"
export const FETCH_SUCCESS_ERROR = "FETCH_SUCCESS_ERROR"
export const FETCH_ERROR_ERROR = "FETCH_ERROR_ERROR"

View File

@@ -0,0 +1,95 @@
import {
CREATE_SYSTEM,
CREATE_SUCCESS_SYSTEM,
CREATE_ERROR_SYSTEM,
UPDATE_SYSTEM,
UPDATE_SUCCESS_SYSTEM,
UPDATE_ERROR_SYSTEM,
DELETE_SYSTEM,
DELETE_SUCCESS_SYSTEM,
DELETE_ERROR_SYSTEM,
FETCH_SYSTEM,
FETCH_SUCCESS_SYSTEM,
FETCH_ERROR_SYSTEM
} from "./system-constants";
function updateObject(oldObject, newValues) {
// Encapsulate the idea of passing a new object as the first parameter
// to Object.assign to ensure we correctly copy data instead of mutating
return Object.assign({}, oldObject, newValues);
}
function updateObjectInArray(array, shortName, updatedItem) {
return array.map( item => {
if(item.shortName !== shortName) {
// This isn't the item we care about - keep it as-is
return item;
}
// Otherwise, this is the one we want - return an updated value
return {
...item,
...updatedItem
};
});
}
const system = (
state = {
isWaiting: false,
items: []},
action
) => {
switch (action.type) {
case UPDATE_SYSTEM:
case FETCH_SYSTEM:
case CREATE_SYSTEM:
return Object.assign({}, state, { isWaiting: true});
case DELETE_SUCCESS_SYSTEM:
return Object.assign({}, state, {
items: state.items.filter(item => item.shortName !== action.data.shortName)
});
case UPDATE_SUCCESS_SYSTEM:
return Object.assign({}, state, {
items: updateObjectInArray(state.items, action.data.shortName, action.data)
});
case FETCH_SUCCESS_SYSTEM:
return Object.assign({}, state, {
isWaiting: false,
items: action.data
});
case CREATE_SUCCESS_SYSTEM:
const newItems = state.items.concat({
name: action.data.name,
shortName: action.data.shortName,
description: action.data.description,
systemType: action.data.systemType,
city: action.data.city,
state: action.data.state,
county: action.data.county,
country: action.data.country,
userId: action.data.userId,
id: action.data.id,
showScreenName: action.data.showScreenName
});
return Object.assign({}, state, { isWaiting: false,
items: newItems});
case UPDATE_ERROR_SYSTEM:
case FETCH_ERROR_SYSTEM:
case CREATE_ERROR_SYSTEM:
return Object.assign({}, state, {
isWaiting: false
});
default:
return state;
}
};
export default system;