I think it is ready...

This commit is contained in:
Luke Berndt
2023-01-23 22:12:54 -05:00
parent a002fb1fe4
commit aa4d343039
11 changed files with 108 additions and 43 deletions

View File

@@ -17,7 +17,6 @@ function get_calls(query, numResults, res) {
var fields = { var fields = {
_id: true, _id: true,
talkgroupNum: true, talkgroupNum: true,
shortName: true,
path: true, path: true,
name: true, name: true,
time: true, time: true,
@@ -39,7 +38,6 @@ function get_calls(query, numResults, res) {
if (item) { if (item) {
call = { call = {
_id: item._id.toHexString(), _id: item._id.toHexString(),
shortName: item.shortName,
talkgroupNum: item.talkgroupNum, talkgroupNum: item.talkgroupNum,
url: item.url, url: item.url,
filename: item.path + item.name, filename: item.path + item.name,
@@ -248,7 +246,7 @@ exports.remove_star = function(req, res, next) {
try { try {
var o_id = ObjectID.createFromHexString(objectId); var o_id = ObjectID.createFromHexString(objectId);
} catch (err) { } catch (err) {
console.warn("[" + req.params.shortName + "] Error - /:shortName/call/:id generating ObjectID " + err); console.warn("[" + req.params.shortName + "] Error - /remove_star/:id generating ObjectID " + err);
res.status(500); res.status(500);
res.send(JSON.stringify({ res.send(JSON.stringify({
success: false, success: false,
@@ -283,7 +281,7 @@ exports.add_star = function(req, res, next) {
try { try {
var o_id = ObjectID.createFromHexString(objectId); var o_id = ObjectID.createFromHexString(objectId);
} catch (err) { } catch (err) {
console.warn("[" + req.params.shortName + "] Error - /:shortName/call/:id generating ObjectID " + err); console.warn("[" + req.params.shortName + "] Error - /add_star/:id generating ObjectID " + err);
res.status(500); res.status(500);
res.send(JSON.stringify({ res.send(JSON.stringify({
success: false, success: false,

View File

@@ -171,6 +171,8 @@ function addSystemClients(req, res, next) {
/*------ CALLS ----------*/ /*------ CALLS ----------*/
app.get('/card/:id', calls.get_card); app.get('/card/:id', calls.get_card);
app.post('/add_star/:id', calls.add_star);
app.post('/remove_star/:id', calls.remove_star);
app.get('/:shortName/call/:id', calls.get_call); app.get('/:shortName/call/:id', calls.get_call);
app.get('/:shortName/calls/latest', calls.get_latest_calls); app.get('/:shortName/calls/latest', calls.get_latest_calls);
app.get('/:shortName/calls/next', calls.get_next_calls); app.get('/:shortName/calls/next', calls.get_next_calls);
@@ -178,8 +180,7 @@ app.get('/:shortName/calls/newer', calls.get_newer_calls);
app.get('/:shortName/calls/older', calls.get_older_calls); app.get('/:shortName/calls/older', calls.get_older_calls);
app.get('/:shortName/calls/:time/older', calls.get_iphone_calls); app.get('/:shortName/calls/:time/older', calls.get_iphone_calls);
app.get('/:shortName/calls', calls.get_calls); app.get('/:shortName/calls', calls.get_calls);
app.post('/:shortName/star/:id', calls.add_star);
app.post('/:shortName/removeStar/:id', calls.remove_star);
/*------ UPLOADS ---------- upload.single('call'), uploads.upload,*/ /*------ UPLOADS ---------- upload.single('call'), uploads.upload,*/
app.post('/:shortName/upload', upload.single('call'), uploads.upload, async function (req, res) { app.post('/:shortName/upload', upload.single('call'), uploads.upload, async function (req, res) {

View File

@@ -7,15 +7,17 @@ import {
import { addStar, removeStar } from "../../features/calls/callsSlice"; import { addStar, removeStar } from "../../features/calls/callsSlice";
import { useGetTalkgroupsQuery } from '../../features/api/apiSlice'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
const CallItem = (props) => { const CallItem = (props) => {
const call = props.call; const call = props.call;
const shortName = props.shortName;
const talkgroups = props.talkgroups;
const activeCall = props.activeCall; const activeCall = props.activeCall;
const [starVisible, setStarVisible] = useState(false); const [starVisible, setStarVisible] = useState(false);
const [starClicked, setStarClicked] = useState(false); const [starClicked, setStarClicked] = useState(false);
const { data: talkgroupsData, isSuccess: isTalkgroupsSuccess } = useGetTalkgroupsQuery(call.shortName);
const dispatch = useDispatch(); const dispatch = useDispatch();
const time = new Date(call.time); const time = new Date(call.time);
@@ -82,10 +84,10 @@ const CallItem = (props) => {
} }
} }
let talkgroup; let talkgroup;
if ((typeof talkgroupsData == 'undefined') || (typeof talkgroupsData.talkgroups[call.talkgroupNum] == 'undefined')) { if ((typeof talkgroups== 'undefined') || (typeof talkgroups.talkgroups[call.talkgroupNum] == 'undefined')) {
talkgroup = call.talkgroupNum; talkgroup = call.talkgroupNum;
} else { } else {
talkgroup = talkgroupsData.talkgroups[call.talkgroupNum].description; talkgroup = talkgroups.talkgroups[call.talkgroupNum].description;
} }
return ( return (
<Table.Row draggable="true" onClick={(e) => props.onClick({ call: call }, e)} {...rowSelected} onDragStart={onDragStart} data-callid={call._id}> <Table.Row draggable="true" onClick={(e) => props.onClick({ call: call }, e)} {...rowSelected} onDragStart={onDragStart} data-callid={call._id}>
@@ -94,8 +96,6 @@ const CallItem = (props) => {
<Table.Cell> {time.toLocaleTimeString()} </Table.Cell> <Table.Cell> {time.toLocaleTimeString()} </Table.Cell>
<Table.Cell onMouseEnter={() => setStarVisible(true)} onMouseLeave={() => setStarVisible(false)} onClick={handleStarClicked}>{starButton}</Table.Cell> <Table.Cell onMouseEnter={() => setStarVisible(true)} onMouseLeave={() => setStarVisible(false)} onClick={handleStarClicked}>{starButton}</Table.Cell>
</Table.Row> </Table.Row>
); );
} }

View File

@@ -1,4 +1,5 @@
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import { useParams } from 'react-router-dom';
import CallItem from "./CallItem"; import CallItem from "./CallItem";
import { import {
Icon, Icon,
@@ -6,11 +7,13 @@ import {
Ref Ref
} from "semantic-ui-react"; } from "semantic-ui-react";
import "../CallPlayer.css"; import "../CallPlayer.css";
import { useGetTalkgroupsQuery } from '../../features/api/apiSlice'
// ---------------------------------------------------- // ----------------------------------------------------
const ListCalls = (props) => { const ListCalls = (props) => {
const activeCallRef = useRef(); const activeCallRef = useRef();
const { shortName } = useParams();
const { data: talkgroupsData, isSuccess: isTalkgroupsSuccess } = useGetTalkgroupsQuery(shortName);
//https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux //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 //https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter
@@ -40,9 +43,9 @@ const ListCalls = (props) => {
{ {
props.callsData.ids.map((callId, index) => { props.callsData.ids.map((callId, index) => {
if (callId === props.activeCallId) { if (callId === props.activeCallId) {
return (<Ref innerRef={activeCallRef} key={index} ><CallItem activeCall={true} call={props.callsData.entities[callId]} key={index} onClick={props.playCall}/></Ref>) return (<Ref innerRef={activeCallRef} key={index} ><CallItem activeCall={true} call={props.callsData.entities[callId]} talkgroups={talkgroupsData} key={index} onClick={props.playCall}/></Ref>)
} else { } else {
return <CallItem activeCall={false} call={props.callsData.entities[callId]} key={index} onClick={props.playCall}/> return <CallItem activeCall={false} call={props.callsData.entities[callId]} talkgroups={talkgroupsData} key={index} onClick={props.playCall}/>
} }
}) })

View File

@@ -15,7 +15,8 @@ import {
Modal, Modal,
Button, Button,
Image, Image,
Form Form,
Message
} from "semantic-ui-react"; } from "semantic-ui-react";
import { useAddNewEventMutation, useGetTalkgroupsQuery } from '../../features/api/apiSlice' import { useAddNewEventMutation, useGetTalkgroupsQuery } from '../../features/api/apiSlice'
@@ -113,6 +114,16 @@ function PlaylistBuilder(props) {
setDescription("") setDescription("")
setPlaylist([]) setPlaylist([])
} }
let content = (<Header as="h3" icon textAlign='center' style={{paddingTop: "3em"}}><Icon name='target' />Drag Calls Here</Header>)
if (playlist.length > 0) {
content = (<Table basic="very">
<Table.Body>
{listItems}
</Table.Body>
</Table>)
}
return ( return (
<> <>
<Modal <Modal
@@ -121,11 +132,16 @@ function PlaylistBuilder(props) {
open={open} open={open}
> >
<Modal.Header>Submit an Event</Modal.Header> <Modal.Header>Submit an Event</Modal.Header>
<Modal.Content image> <Modal.Content>
<Image size='medium' src='https://react.semantic-ui.com/images/avatar/large/rachel.png' wrapped />
<Modal.Description> <Modal.Description>
<Message icon>
<Icon name='warning sign' />
<Message.Content>The Events feature is a work in progress! Things may break, change, or get deleted. Contact me with ideas or problems: luke@robotastic.com</Message.Content>
</Message>
<Header>Event</Header> <Header>Event</Header>
<Form> <Form>
<Form.Input <Form.Input
name="title" name="title"
value={title} value={title}
@@ -139,8 +155,6 @@ function PlaylistBuilder(props) {
placeholder='Please describe the event' placeholder='Please describe the event'
onChange={handleDescriptionChange} onChange={handleDescriptionChange}
/> />
</Form> </Form>
</Modal.Description> </Modal.Description>
</Modal.Content> </Modal.Content>
@@ -158,11 +172,7 @@ function PlaylistBuilder(props) {
</Modal.Actions> </Modal.Actions>
</Modal> </Modal>
<Tab.Pane attached='bottom' onDragOver={onDragOver} onDrop={onDrop}> <Tab.Pane attached='bottom' onDragOver={onDragOver} onDrop={onDrop}>
<Table basic="very"> {content}
<Table.Body>
{listItems}
</Table.Body>
</Table>
</Tab.Pane> </Tab.Pane>
<Menu fluid widths={2}> <Menu fluid widths={2}>
<Menu.Item name="clear" onClick={() => setPlaylist([])}><Icon name='delete' />Clear</Menu.Item> <Menu.Item name="clear" onClick={() => setPlaylist([])}><Icon name='delete' />Clear</Menu.Item>

View File

@@ -13,10 +13,6 @@ import {
import CallInfoPane from "../Call/components/CallInfoPane" import CallInfoPane from "../Call/components/CallInfoPane"
// ---------------------------------------------------- // ----------------------------------------------------
function EventCallInfo(props) { function EventCallInfo(props) {
return ( return (
<Segment> <Segment>
<CallInfoPane call={props.call} /> <CallInfoPane call={props.call} />

View File

@@ -0,0 +1,52 @@
import React, { useState } from "react";
import {
Table,
Icon,
Label
} from "semantic-ui-react";
import { useGetTalkgroupsQuery } from '../features/api/apiSlice'
const EventCallItem = (props) => {
const call = props.call;
const activeCall = props.activeCall;
const { data: talkgroupsData, isSuccess: isTalkgroupsSuccess } = useGetTalkgroupsQuery(call.shortName);
const time = new Date(call.time);
let rowSelected = {};
if (activeCall) {
rowSelected = {
positive: true,
color: "blue",
key: "blue",
inverted: "true"
}
}
let talkgroup;
if ((typeof talkgroupsData == 'undefined') || (typeof talkgroupsData.talkgroups[call.talkgroupNum] == 'undefined')) {
talkgroup = call.talkgroupNum;
} else {
talkgroup = talkgroupsData.talkgroups[call.talkgroupNum].description;
}
return (
<Table.Row onClick={(e) => props.onClick({ call: call }, e)} {...rowSelected}>
<Table.Cell> {call.len} </Table.Cell>
<Table.Cell> {talkgroup} </Table.Cell>
<Table.Cell> {time.toLocaleTimeString()} </Table.Cell>
<Table.Cell> {call.shortName}</Table.Cell>
</Table.Row>
);
}
export default EventCallItem;

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import CallItem from "../Call/components/CallItem"; import EventCallItem from "./EventCallItem";
import { import {
Icon, Icon,
Table, Table,
@@ -31,7 +31,7 @@ const ListEventCalls = (props) => {
<Table.HeaderCell>Len</Table.HeaderCell> <Table.HeaderCell>Len</Table.HeaderCell>
<Table.HeaderCell>Talkgroup</Table.HeaderCell> <Table.HeaderCell>Talkgroup</Table.HeaderCell>
<Table.HeaderCell>Time</Table.HeaderCell> <Table.HeaderCell>Time</Table.HeaderCell>
<Table.HeaderCell><Icon name='star' /></Table.HeaderCell> <Table.HeaderCell>System</Table.HeaderCell>
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>
{props.eventData && {props.eventData &&
@@ -39,9 +39,9 @@ const ListEventCalls = (props) => {
{ props.eventData.calls.map((call, index) => { { props.eventData.calls.map((call, index) => {
if (call._id === props.activeCallId) { if (call._id === props.activeCallId) {
return (<Ref innerRef={activeCallRef} key={index} ><CallItem activeCall={true} call={call} key={index} onClick={props.playCall}/></Ref>) return (<Ref innerRef={activeCallRef} key={index} ><EventCallItem activeCall={true} call={call} key={index} onClick={props.playCall} showStar={false}/></Ref>)
} else { } else {
return <CallItem activeCall={false} call={call} key={index} onClick={props.playCall}/> return <EventCallItem activeCall={false} call={call} key={index} onClick={props.playCall} showStar={false}/>
} }
}) })

View File

@@ -68,21 +68,23 @@ function ViewEvent(props) {
const dateRange = (start, end) => { const dateRange = (start, end) => {
if (start.getYear() != end.getYear()) { if (start.getYear() != end.getYear()) {
return {"part1": start.toLocaleDateString('en-us', { weekday:"short", year:"numeric", month:"short", day:"numeric"}) + " " + start.toLocaleTimeString(), "part2": end.toLocaleDateString('en-us', { weekday:"short", year:"numeric", month:"short", day:"numeric"}) + " " + end.toLocaleTimeString()}; return {"part1": start.toLocaleDateString('en-us', { weekday:"short", year:"numeric", month:"short", day:"numeric"}) + " " + start.toLocaleTimeString([], {hour: 'numeric', minute:'2-digit'}), "part2": end.toLocaleDateString('en-us', { weekday:"short", year:"numeric", month:"short", day:"numeric"}) + " " + end.toLocaleTimeString([], {hour: 'numeric', minute:'2-digit'})};
} else if (start.getMonth() != end.getMonth()) { } else if (start.getMonth() != end.getMonth()) {
return {"part1": start.toLocaleDateString('en-us', { weekday:"short", month:"short", day:"numeric"}) + " " + start.toLocaleTimeString(), "part2": end.toLocaleDateString('en-us', { weekday:"short", month:"short", day:"numeric"}) + " " + end.toLocaleTimeString()}; return {"part1": start.toLocaleDateString('en-us', { weekday:"short", month:"short", day:"numeric"}) + " " + start.toLocaleTimeString([], {hour: 'numeric', minute:'2-digit'}), "part2": end.toLocaleDateString('en-us', { weekday:"short", month:"short", day:"numeric"}) + " " + end.toLocaleTimeString([], {hour: 'numeric', minute:'2-digit'})};
} else if (start.getDay() != end.getDay()) { } else if (start.getDay() != end.getDay()) {
return {"part1": start.toLocaleDateString('en-us', { weekday:"short", month:"short", day:"numeric"}) + " " + start.toLocaleTimeString(), "part2": end.toLocaleDateString('en-us', { weekday:"short", day:"numeric"}) + " " + end.toLocaleTimeString()}; return {"part1": start.toLocaleDateString('en-us', { weekday:"short", month:"short", day:"numeric"}) + " " + start.toLocaleTimeString([], {hour: 'numeric', minute:'2-digit'}), "part2": end.toLocaleDateString('en-us', { weekday:"short", day:"numeric"}) + " " + end.toLocaleTimeString([], {hour: 'numeric', minute:'2-digit'})};
} else { } else {
return {"part1": start.toLocaleDateString('en-us', { weekday:"short", month:"short", day:"numeric"}) + " " + start.toLocaleTimeString(), "part2": end.toLocaleTimeString()}; return {"part1": start.toLocaleDateString('en-us', { weekday:"short", month:"short", day:"numeric"}) + " " + start.toLocaleTimeString([], {hour: 'numeric', minute:'2-digit'}), "part2": end.toLocaleTimeString([], {hour: 'numeric', minute:'2-digit'})};
} }
} }
let dates = {part1: "", part2: ""} let dates = {part1: "", part2: ""}
let expirationDate = ""
if (eventData) { if (eventData) {
dates = dateRange(new Date(eventData.startTime),new Date(eventData.endTime)) dates = dateRange(new Date(eventData.startTime),new Date(eventData.endTime))
expirationDate = new Date(eventData.expireTime).toLocaleDateString('en-us', { weekday:"short", month:"short", day:"numeric"})
} }
/* /*
const dateRange = (start, end) => { const dateRange = (start, end) => {
@@ -126,11 +128,16 @@ function ViewEvent(props) {
<Grid.Column mobile={1} tablet={1} computer={1} ></Grid.Column> <Grid.Column mobile={1} tablet={1} computer={1} ></Grid.Column>
<Grid.Column mobile={16} tablet={8} computer={4}> <Grid.Column mobile={16} tablet={8} computer={4}>
<Container text > <Container text >
{eventData && eventData.description} {eventData&&
<>
<p>{eventData.description}</p>
<Header size='tiny'>Expires {expirationDate}</Header>
</>
}
</Container> </Container>
</Grid.Column> </Grid.Column>
<Grid.Column mobile={8} tablet={8} computer={4}> <Grid.Column mobile={8} tablet={8} computer={4}>
<Header as="h2">{dates.part1} → <br/>{dates.part2}</Header> <Header as="h2">{dates.part1} <br/>→ {dates.part2}</Header>
</Grid.Column> </Grid.Column>
<Grid.Column mobile={8} tablet={8} computer={2}> <Grid.Column mobile={8} tablet={8} computer={2}>

View File

@@ -129,8 +129,7 @@ const initialState = {
export const addStar = createAsyncThunk( export const addStar = createAsyncThunk(
'calls/addStar', 'calls/addStar',
async(callId,{getState, requestIdleCallback}) => { async(callId,{getState, requestIdleCallback}) => {
const {shortName} = getState().callPlayer; const url = process.env.REACT_APP_BACKEND_SERVER + "/add_star/" + callId
const url = process.env.REACT_APP_BACKEND_SERVER + "/" + shortName.toLowerCase() + "/star/" + callId
const res = await fetch(url, { const res = await fetch(url, {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -148,8 +147,7 @@ export const addStar = createAsyncThunk(
export const removeStar = createAsyncThunk( export const removeStar = createAsyncThunk(
'calls/removeStar', 'calls/removeStar',
async(callId,{getState, requestIdleCallback}) => { async(callId,{getState, requestIdleCallback}) => {
const {shortName} = getState().callPlayer; const url = process.env.REACT_APP_BACKEND_SERVER + "/remove_star/" + callId
const url = process.env.REACT_APP_BACKEND_SERVER + "/" + shortName.toLowerCase() + "/removeStar/" + callId
const res = await fetch(url, { const res = await fetch(url, {
method: 'POST', method: 'POST',
headers: { headers: {