@@ -216,4 +216,4 @@ class TurnToFinalHeaderComponents extends React.Component {
}
};
-export { TimeHeader, TurnToFinalHeaderComponents };
+export { TimeHeader, TurnToFinalHeaderComponents };
\ No newline at end of file
diff --git a/src/main/javascript/trends.js b/src/main/javascript/trends.js
index e1c410d6e..0287a4561 100644
--- a/src/main/javascript/trends.js
+++ b/src/main/javascript/trends.js
@@ -47,9 +47,16 @@ class TrendsPage extends React.Component {
constructor(props) {
super(props);
let eventChecked = {};
+ let eventsEmpty = {};
+
+ eventNames.unshift("ANY Event");
for (let i = 0; i < eventNames.length; i++) {
- eventChecked[eventNames[i]] = false;
+
+ let eventNameCur = eventNames[i];
+ eventChecked[eventNameCur] = false;
+ eventsEmpty[eventNameCur] = true;
}
+ eventsEmpty["ANY Event"] = false;
var date = new Date();
this.state = {
@@ -60,7 +67,8 @@ class TrendsPage extends React.Component {
endMonth : date.getMonth() + 1,
datesChanged : false,
aggregatePage : props.aggregate_page,
- eventChecked : eventChecked
+ eventChecked : eventChecked,
+ eventsEmpty : eventsEmpty
};
this.fetchMonthlyEventCounts();
@@ -91,26 +99,84 @@ class TrendsPage extends React.Component {
};
let trendsPage = this;
- $.ajax({
- type: 'POST',
- url: '/protected/monthly_event_counts',
- data : submission_data,
- dataType : 'json',
- success : function(response) {
- $('#loading').hide();
-
- if (response.err_msg) {
- errorModal.show(response.err_title, response.err_msg);
- return;
- }
-
- eventCounts = response;
- trendsPage.displayPlots(trendsPage.state.airframe);
- },
- error : function(jqXHR, textStatus, errorThrown) {
- errorModal.show("Error Loading Uploads", errorThrown);
- },
- async: true
+
+ $('#loading').hide();
+
+ return new Promise((resolve, reject) => {
+ $.ajax({
+ type: 'POST',
+ url: '/protected/monthly_event_counts',
+ data : submission_data,
+ dataType : 'json',
+ success : function(response) {
+
+ if (response.err_msg) {
+ errorModal.show(response.err_title, response.err_msg);
+ return;
+ }
+
+ eventCounts = response;
+
+ let countsMerged = {};
+ for(let [eventName, countsObject] of Object.entries(eventCounts)) {
+
+ for(let [airframeName] of Object.entries(countsObject)) {
+
+ if (airframeName === "Garmin Flight Display") {
+ continue;
+ }
+
+ let countsAirframe = countsObject[airframeName];
+
+ //Airframe name is not in the merged counts object yet, add it
+ if (!(airframeName in countsMerged)) {
+
+ countsMerged[airframeName] = {
+ airframeName: airframeName,
+ eventName: "ANY Event",
+ dates: [...countsAirframe.dates],
+ aggregateFlightsWithEventCounts: [...countsAirframe.aggregateFlightsWithEventCounts],
+ aggregateTotalEventsCounts: [...countsAirframe.aggregateTotalEventsCounts],
+ aggregateTotalFlightsCounts: [...countsAirframe.aggregateTotalFlightsCounts],
+ flightsWithEventCounts: [...countsAirframe.flightsWithEventCounts],
+ totalEventsCounts: [...countsAirframe.totalEventsCounts],
+ totalFlightsCounts: [...countsAirframe.totalFlightsCounts]
+ };
+
+ //Airframe name is already in the merged counts object, add the counts
+ } else {
+
+ for (let i = 0 ; i < countsAirframe.dates.length ; i++) {
+
+ if (countsAirframe.totalEventsCounts[i] === 0)
+ continue;
+
+ countsMerged[airframeName].aggregateFlightsWithEventCounts[i] += countsAirframe.aggregateFlightsWithEventCounts[i];
+ countsMerged[airframeName].aggregateTotalEventsCounts[i] += countsAirframe.aggregateTotalEventsCounts[i];
+ countsMerged[airframeName].aggregateTotalFlightsCounts[i] += countsAirframe.aggregateTotalFlightsCounts[i];
+ countsMerged[airframeName].flightsWithEventCounts[i] += countsAirframe.flightsWithEventCounts[i];
+ countsMerged[airframeName].totalEventsCounts[i] += countsAirframe.totalEventsCounts[i];
+ countsMerged[airframeName].totalFlightsCounts[i] += countsAirframe.totalFlightsCounts[i];
+ }
+
+ }
+
+ }
+
+ }
+
+ eventCounts["ANY Event"] = countsMerged;
+
+ trendsPage.displayPlots(trendsPage.state.airframe);
+
+ resolve(response);
+ },
+ error : function(jqXHR, textStatus, errorThrown) {
+ errorModal.show("Error Loading Uploads", errorThrown);
+ reject(errorThrown);
+ },
+ async: true
+ });
});
}
@@ -311,15 +377,29 @@ class TrendsPage extends React.Component {
countData = [];
percentData = [];
+
+
let counts = eventCounts == null ? {} : eventCounts;
+ let airframeNames = [];
+ for (let [eventName, countsObject] of Object.entries(counts)) {
+ for (let [airframe, value] of Object.entries(countsObject)) {
+ if (value.airframeName === "Garmin Flight Display") continue;
+ if (!airframeNames.includes(value.airframeName)) {
+ airframeNames.push(value.airframeName);
+ }
+ }
+ }
+
for (let [eventName, countsObject] of Object.entries(counts)) {
//console.log("checking to plot event: '" + eventName + "', checked? '" + this.state.eventChecked[eventName] + "'");
if (!this.state.eventChecked[eventName]) continue;
+
let fleetPercents = null;
let ngafidPercents = null;
+
if (eventName in eventFleetPercents) {
console.log('getting existing fleetPercents!');
@@ -373,18 +453,69 @@ class TrendsPage extends React.Component {
console.log(value);
*/
-
value.name = value.eventName + " - " + value.airframeName;
value.x = value.dates;
value.type = 'scatter';
value.hoverinfo = 'x+text';
+
+ let airframeIndex = airframes.indexOf(value.airframeName);
+ let eventNameIndex = eventNames.indexOf(eventName);
+
+ let indexCur = (airframeIndex + eventNameIndex);
+ let indicesMax = (airframes.length + eventNames.length);
+
+ //Dashed lines for ANY Event
+ if (eventName === "ANY Event") {
+
+ value = {
+ ...value,
+ legendgroup: value.name,
+
+ //Consistent rainbow colors for each airframe
+ line : {
+ width: 1.0,
+ dash: 'dot',
+ color : 'hsl(' + parseInt(360.0 * airframeIndex / airframeNames.length) + ', 50%, 50%)'
+ }
+
+ };
+
+ //'Glowing' rainbow lines for other events
+ } else {
+
+ value = {
+ ...value,
+ legendgroup: value.name,
+ //showlegend: false,
+
+ mode : 'lines',
+ line : {
+ width : 2,
+ // color : 'hsl('
+ // + parseInt(360.0 * indexCur / indicesMax) + ','
+ // + parseInt(50.0 + 50.0 * airframeIndex / airframeNames.length) + '%,'
+ // + parseInt(25.0 + 25.0) + '%)'
+ }
+
+ };
+ }
+
//don't add airframes to the count plot that the fleet doesn't have
- if (airframes.indexOf(value.airframeName) >= 0) countData.push(value);
+ if (airframes.indexOf(value.airframeName) >= 0) {
+
+ //Display the "ANY Event" lines under the other ones
+ if (eventName === "ANY Event") {
+ countData.unshift(value);
+ } else {
+ countData.push(value);
+ }
+
+ }
+
if (this.state.aggregatePage) {
value.y = value.aggregateTotalEventsCounts;
- }
- else {
+ } else {
value.y = value.totalEventsCounts;
}
value.hovertext = [];
@@ -427,15 +558,45 @@ class TrendsPage extends React.Component {
if (this.state.aggregatePage) {
flightsWithEventCount = value.aggregateFlightsWithEventCounts[i];
totalFlightsCount = value.aggregateTotalFlightsCounts[i];
- }
- else {
+ } else {
flightsWithEventCount = value.flightsWithEventCounts[i];
totalFlightsCount = value.totalFlightsCounts[i];
}
value.hovertext.push(value.y[i] + " events in " + flightsWithEventCount + " of " + totalFlightsCount + " flights : " + value.eventName + " - " + value.airframeName);
}
-
+
}
+
+ }
+
+ for (const airframeName of airframeNames) {
+
+ let airframeIndex = airframeNames.indexOf(airframeName);
+ let airframeLegendHighlight = {
+
+ name : airframeName,
+
+ x : [0],
+ y : [0],
+
+ //visible: 'legendonly',
+ visible: false,
+ showlegend: true,
+
+ mode : 'markers',
+ marker : {
+ width : 2.0,
+ opacity: 1.0,
+ // color : 'hsl('
+ // + parseInt(360.0 * airframeIndex / airframeNames.length) + ','
+ // + parseInt(50.0) + '%,'
+ // + parseInt(50.0) + '%)'
+ }
+
+ };
+
+ countData.push(airframeLegendHighlight);
+
}
/*
@@ -472,6 +633,58 @@ class TrendsPage extends React.Component {
fleetValue.hovertext.push(fixedText + " (" + fleetValue.flightsWithEventCounts[date] + " of " + fleetValue.totalFlightsCounts[date] + " flights) : " + fleetValue.name);
}
}
+
+
+
+ let airframeIndex = airframes.indexOf(ngafidValue.airframeName);
+ let eventNameIndex = eventNames.indexOf(eventName);
+
+ let indexCur = (airframeIndex + eventNameIndex);
+ let indicesMax = (airframes.length + eventNames.length);
+
+ //...
+ if (eventName === "ANY Event") {
+
+ ngafidValue = {
+ ...ngafidValue,
+
+ legendgroup: ngafidValue.name,
+
+ //Consistent rainbow colors for each event
+ line : {
+ width: 1,
+ dash: 'dot',
+ color : 'hsl(' + parseInt(360.0 * airframeIndex / airframeNames.length) + ', 50%, 50%)'
+ }
+
+ };
+
+ //...
+ } else {
+
+ ngafidValue = {
+ ...ngafidValue,
+
+ legendgroup: ngafidValue.name,
+ mode : 'lines',
+
+ //Consistent rainbow colors for each event
+ line : {
+ width : 2,
+ // color : 'hsl('
+ // + parseInt(360.0 * eventNameIndex / eventNames.length)
+ // + parseInt(50.0 + 50.0 * airframeIndex / airframeNames.length) + '%,'
+ // + parseInt(25.0 + 25.0 * airframeIndex / airframeNames.length) + '%)'
+ color : 'hsl('
+ + parseInt(360.0 * indexCur / indicesMax) + ','
+ + parseInt(50.0 + 50.0 * airframeIndex / airframeNames.length) + '%,'
+ + parseInt(25.0 + 25.0) + '%)'
+// + parseInt(25.0 + 25.0 * (indexCur%2)) + '%)'
+ }
+
+ };
+ }
+
percentData.push(ngafidValue);
ngafidValue.x = [];
ngafidValue.y = [];
@@ -483,6 +696,7 @@ class TrendsPage extends React.Component {
ngafidValue.y.push(v);
+
//console.log(date + " :: " + ngafidValue.flightsWithEventCounts[date] + " / " + ngafidValue.totalFlightsCounts[date] + " : " + v);
//this will give 2 significant figures (and leading 0s if it is quite small)
@@ -545,39 +759,20 @@ class TrendsPage extends React.Component {
Plotly.newPlot('count-trends-plot', countData, countLayout, config);
Plotly.newPlot('percent-trends-plot', percentData, percentLayout, config);
+ console.log("Hiding loading spinner");
+ $('#loading').hide();
}
checkEvent(eventName) {
+
console.log("checking event: '" + eventName + "'");
- console.log("Changing event state : " + eventName + " from " + this.state.eventChecked[eventName] + " to " + !this.state.eventChecked[eventName]);
this.state.eventChecked[eventName] = !this.state.eventChecked[eventName];
this.setState(this.state);
- let startDate = this.state.startYear + "-";
- let endDate = this.state.endYear + "-";
-
- //0 pad the months on the front
- if (parseInt(this.state.startMonth) < 10) startDate += "0" + parseInt(this.state.startMonth);
- else startDate += this.state.startMonth;
- if (parseInt(this.state.endMonth) < 10) endDate += "0" + parseInt(this.state.endMonth);
- else endDate += this.state.endMonth;
+ this.displayPlots(this.state.airframe);
- var submission_data = {
- startDate : startDate + "-01",
- endDate : endDate + "-28",
- eventName : eventName,
- aggregatePage : this.props.aggregate_page
- };
- console.log(eventCounts);
- if (eventCounts != null) {
- console.log("already loaded counts for event: '" + eventName + "'");
- this.displayPlots(this.state.airframe);
- } else {
- $('#loading').show();
- console.log("showing loading spinner!");
- }
}
updateStartYear(newStartYear) {
@@ -611,11 +806,25 @@ class TrendsPage extends React.Component {
this.state.eventChecked[eventName] = false;
}
this.state.datesChanged = false;
- this.setState(this.state);
+ $('#loading').hide();
+
+ this.fetchMonthlyEventCounts().then((data) => {
+
+ //Set all events to empty initially
+ for (let i = 0; i < eventNames.length; i++) {
+ let eventNameCur = eventNames[i];
+ this.state.eventsEmpty[eventNameCur] = true;
+ }
+
+ for (let [eventName, countsObject] of Object.entries(data)) {
+ this.state.eventsEmpty[eventName] = false;
+ }
+
+ this.setState(this.state);
+ this.displayPlots(this.state.airframe);
+
+ });
- eventCounts = null;
- this.fetchMonthlyEventCounts();
- this.displayPlots(this.state.airframe);
}
airframeChange(airframe) {
@@ -624,7 +833,6 @@ class TrendsPage extends React.Component {
}
render() {
- //console.log(systemIds);
const numberOptions = {
minimumFractionDigits: 2,
@@ -656,29 +864,38 @@ class TrendsPage extends React.Component {
updateEndYear={(newEndYear) => this.updateEndYear(newEndYear)}
updateEndMonth={(newEndMonth) => this.updateEndMonth(newEndMonth)}
exportCSV={() => this.exportCSV()}
-
-
/>
-
{
eventNames.map((eventName, index) => {
+
+ //Don't show a description for the "ANY Event" event
+ if (eventName === "ANY Event") return (
+
+ this.checkEvent(eventName)}>
+
+
+ );
+
return (
-
this.checkEvent(eventName)}>
-
+ onChange={() => this.checkEvent(eventName)}>
+
(
{GetDescription(eventName)})}
placement="bottom">
-
diff --git a/src/main/javascript/uploads.js b/src/main/javascript/uploads.js
index 74d5c57fd..d71f46132 100644
--- a/src/main/javascript/uploads.js
+++ b/src/main/javascript/uploads.js
@@ -20,6 +20,7 @@ var chunkSize = 2 * 1024 * 1024; //2MB
class Upload extends React.Component {
constructor(props) {
super(props);
+ this.isFleetManager = this.props.isFleetManager;
}
componentDidMount() {
@@ -153,8 +154,8 @@ class Upload extends React.Component {
console.log("uploadInfo:");
console.log(uploadInfo);
- //Disable Download/Delete buttons while Upload HASHING / UPLOADING
- let doButtonDisplay = (status!="HASHING" && status!="UPLOADING");
+ //Disable Download/Delete buttons while Hashing/Uploading or if not a Fleet Manager
+ let doButtonDisplay = (this.isFleetManager && status!="HASHING" && status!="UPLOADING");
return (
@@ -651,6 +652,9 @@ class UploadsPage extends React.Component {
display : "none"
};
+ //Disable Upload Flights button if not a Fleet Manager
+ let doButtonDisplay = (fleetManager);
+
return (
@@ -666,7 +670,12 @@ class UploadsPage extends React.Component {
? ( )
: ""
}
-
@@ -682,6 +691,7 @@ class UploadsPage extends React.Component {
//uploadInfo.position = index;
return (
{ this.removePendingUpload(uploadInfo); } }
@@ -709,7 +719,7 @@ class UploadsPage extends React.Component {
this.state.uploads.map((uploadInfo, index) => {
uploadInfo.position = index;
return (
- {this.removeUpload(uploadInfo);}} />
+ {this.removeUpload(uploadInfo);}} />
);
})
}