AG-420 Improve React implementation
This commit is contained in:
16
src-trader-dashboard/actions/fxDataActions.js
Normal file
16
src-trader-dashboard/actions/fxDataActions.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
|
||||
export function fxDataUpdated(fxData) {
|
||||
return {
|
||||
type: 'FX_DATA_CHANGED',
|
||||
fxData: cloneDeep(fxData)
|
||||
};
|
||||
}
|
||||
|
||||
export function fxTopMoversUpdated(fxTopMovers) {
|
||||
return {
|
||||
type: 'FX_TOP_MOVERS_CHANGED',
|
||||
fxTopMovers: cloneDeep(fxTopMovers)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
|
||||
export default function fxDataUpdatedAction(fxData) {
|
||||
return {
|
||||
type: 'FX_DATA_CHANGED',
|
||||
fxData: cloneDeep(fxData)
|
||||
};
|
||||
}
|
||||
|
||||
26
src-trader-dashboard/components/FxPanel.jsx
Normal file
26
src-trader-dashboard/components/FxPanel.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
import FxDataService from "../services/FxDataService.jsx";
|
||||
import FxQuoteMatrix from "./FxQuoteMatrix.jsx";
|
||||
import TopMoversGrid from "./TopMoversGrid.jsx";
|
||||
|
||||
export default class extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.fxDataService = new FxDataService();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div style={{float: "left", marginRight: 25}}>
|
||||
<FxQuoteMatrix fxDataService={this.fxDataService}/>
|
||||
</div>
|
||||
<div style={{float: "left"}}>
|
||||
<TopMoversGrid fxDataService={this.fxDataService}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -5,18 +5,13 @@ import {AgGridReact} from "ag-grid-react";
|
||||
|
||||
import assign from "lodash/assign";
|
||||
import uniq from "lodash/uniq";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
|
||||
import FxDataService from "../services/FxDataService.jsx";
|
||||
|
||||
class FxQuoteMatrix extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.fxDataService = new FxDataService();
|
||||
|
||||
this.state = {
|
||||
columnDefs: this.fxDataService.getFxMatrixHeaderNames()
|
||||
columnDefs: this.props.fxDataService.getFxMatrixHeaderNames()
|
||||
};
|
||||
|
||||
// grid events
|
||||
@@ -28,48 +23,43 @@ class FxQuoteMatrix extends Component {
|
||||
this.columnApi = params.columnApi;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.gridApi.setRowData(this.props.rowData);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!this.gridApi) {
|
||||
return;
|
||||
}
|
||||
const newRowData = nextProps.rowData;
|
||||
|
||||
if (!this.props.rowData ||
|
||||
this.props.rowData.length === 0) {
|
||||
this.gridApi.setRowData(nextProps.rowData);
|
||||
} else {
|
||||
const newRowData = nextProps.rowData;
|
||||
const updatedNodes = [];
|
||||
const updatedCols = [];
|
||||
|
||||
const updatedNodes = [];
|
||||
const updatedCols = [];
|
||||
for (let i = 0; i < newRowData.length; i++) {
|
||||
// note that for this use case we assume the existing and new row data have the same
|
||||
// row and column order
|
||||
let node = this.gridApi.getModel().getRow(i);
|
||||
let newRow = newRowData[i];
|
||||
|
||||
for (let i = 0; i < newRowData.length; i++) {
|
||||
// note that for this use case we assume the existing and new row data have the same
|
||||
// row and column order
|
||||
let node = this.gridApi.getModel().getRow(i);
|
||||
let newRow = newRowData[i];
|
||||
const {data} = node;
|
||||
let updated = false;
|
||||
for (const def of this.state.columnDefs) {
|
||||
if (data[def.field] !== newRow[def.field]) {
|
||||
updatedCols.push(def.field);
|
||||
|
||||
const {data} = node;
|
||||
let updated = false;
|
||||
for (const def of this.state.columnDefs) {
|
||||
if (data[def.field] !== newRow[def.field]) {
|
||||
updatedCols.push(def.field);
|
||||
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
if(updated) {
|
||||
assign(data, newRow);
|
||||
updatedNodes.push(node);
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.gridApi.refreshCells(updatedNodes, uniq(updatedCols));
|
||||
if(updated) {
|
||||
assign(data, newRow);
|
||||
updatedNodes.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
this.gridApi.refreshCells(updatedNodes, uniq(updatedCols));
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{height: 410, width: "100%"}}
|
||||
<div style={{height: 410, width: 800}}
|
||||
className="ag-fresh">
|
||||
<AgGridReact
|
||||
// properties
|
||||
|
||||
@@ -41,15 +41,6 @@ export default class extends Component {
|
||||
cellFormatter: this.numberFormatter,
|
||||
cellRenderer: 'animateShowChange',
|
||||
cellStyle: {'text-align': 'right'}
|
||||
},
|
||||
{
|
||||
headerName: 'Recommendation',
|
||||
field: 'recommendation',
|
||||
cellEditor: 'richSelect',
|
||||
cellEditorParams: {
|
||||
values: ['Buy', 'Hold', 'Sell']
|
||||
},
|
||||
editable: true
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, {Component} from "react";
|
||||
|
||||
import PriceChangesGrid from "./PriceChangesGrid.jsx";
|
||||
import StockDetailPanel from "./StockDetailPanel.jsx";
|
||||
import FxQuoteMatrix from "./FxQuoteMatrix.jsx";
|
||||
import FxPanel from "./FxPanel.jsx";
|
||||
|
||||
export default class extends Component {
|
||||
constructor(props) {
|
||||
@@ -48,7 +48,7 @@ export default class extends Component {
|
||||
</div>
|
||||
</div>
|
||||
<div style={{width: "100%", clear: "both", paddingTop: 25}}>
|
||||
<FxQuoteMatrix/>
|
||||
<FxPanel/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
105
src-trader-dashboard/components/TopMoversGrid.jsx
Normal file
105
src-trader-dashboard/components/TopMoversGrid.jsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import React, {Component} from "react";
|
||||
import {connect} from "react-redux";
|
||||
|
||||
import {AgGridReact} from "ag-grid-react";
|
||||
|
||||
class TopMoversGrid extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
columnDefs: [
|
||||
{
|
||||
field: 'symbol',
|
||||
headerName: 'Symbol'
|
||||
},
|
||||
{
|
||||
field: 'last',
|
||||
headerName: 'Last',
|
||||
headerClass: 'align-right',
|
||||
cellRenderer: 'animateShowChange',
|
||||
cellClass: 'align-right'
|
||||
},
|
||||
{
|
||||
field: 'net',
|
||||
headerName: 'Net',
|
||||
headerClass: 'align-right',
|
||||
cellRenderer: 'animateShowChange',
|
||||
cellClass: 'align-right'
|
||||
},
|
||||
{
|
||||
field: 'pct_net_change',
|
||||
headerName: '% NC',
|
||||
headerClass: 'align-right',
|
||||
cellRenderer: 'animateShowChange',
|
||||
cellClass: 'align-right'
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
// grid events
|
||||
this.onGridReady = this.onGridReady.bind(this);
|
||||
}
|
||||
|
||||
onGridReady(params) {
|
||||
this.gridApi = params.api;
|
||||
this.columnApi = params.columnApi;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.gridApi.setRowData(this.props.rowData);
|
||||
this.gridApi.sizeColumnsToFit();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
let newRowData = nextProps.rowData;
|
||||
let model = this.gridApi.getModel();
|
||||
|
||||
// remove nodes not in new data set
|
||||
let nodesToRemove = [];
|
||||
for (let rowIndex = 0; rowIndex < model.getRowCount(); rowIndex++) {
|
||||
let rowNode = model.getRow(rowIndex);
|
||||
if (rowNode.data.symbol !== newRowData[rowIndex].symbol) {
|
||||
nodesToRemove.push(rowNode);
|
||||
}
|
||||
}
|
||||
this.gridApi.removeItems(nodesToRemove);
|
||||
|
||||
// add new items in set
|
||||
for (let rowIndex = 0; rowIndex < newRowData.length; rowIndex++) {
|
||||
let model = this.gridApi.getModel();
|
||||
let rowNode = model.getRow(rowIndex);
|
||||
|
||||
if (!rowNode ||
|
||||
rowNode.data.symbol !== newRowData[rowIndex].symbol) {
|
||||
this.gridApi.insertItemsAtIndex(rowIndex, [newRowData[rowIndex]])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{height: 410, width: 400}}
|
||||
className="ag-fresh">
|
||||
<AgGridReact
|
||||
// properties
|
||||
columnDefs={this.state.columnDefs}
|
||||
enableSorting="false"
|
||||
enableFilter="false"
|
||||
animateRows="true"
|
||||
|
||||
// events
|
||||
onGridReady={this.onGridReady}>
|
||||
</AgGridReact>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state) => {
|
||||
return {
|
||||
rowData: state.fxTopMovers
|
||||
}
|
||||
}
|
||||
)(TopMoversGrid);
|
||||
@@ -1,10 +0,0 @@
|
||||
export default (state = {fxData: []}, action) => {
|
||||
switch (action.type) {
|
||||
case 'FX_DATA_CHANGED':
|
||||
return {
|
||||
fxData: action.fxData.slice(0)
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
16
src-trader-dashboard/reducers/fxDataReducer.jsx
Normal file
16
src-trader-dashboard/reducers/fxDataReducer.jsx
Normal file
@@ -0,0 +1,16 @@
|
||||
export default (state = {fxData: [], fxTopMovers: []}, action) => {
|
||||
switch (action.type) {
|
||||
case 'FX_DATA_CHANGED':
|
||||
return {
|
||||
...state,
|
||||
fxData: action.fxData.slice(0),
|
||||
};
|
||||
case 'FX_TOP_MOVERS_CHANGED':
|
||||
return {
|
||||
...state,
|
||||
fxTopMovers: action.fxTopMovers.slice(0),
|
||||
};
|
||||
default:
|
||||
return action.state;
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,8 @@
|
||||
import sampleSize from "lodash/sampleSize";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
|
||||
import StoreService from "./StoreService";
|
||||
import fxDataUpdated from "../actions/fxDataUpdated";
|
||||
import {fxDataUpdated, fxTopMoversUpdated} from "../actions/fxDataActions";
|
||||
|
||||
import HorizontalBarComponent from "../components/renderers/HorizontalBarComponent.jsx";
|
||||
|
||||
@@ -9,14 +10,32 @@ export default class {
|
||||
constructor() {
|
||||
this.store = StoreService.STORE;
|
||||
|
||||
this.fxData = this.createFxMatrixSnapshot();
|
||||
this.createFxMatrixSnapshot();
|
||||
this.calculateTopMovers();
|
||||
|
||||
this.store.dispatch(fxDataUpdated(this.fxData));
|
||||
this.store.dispatch(fxTopMoversUpdated(this.fxTopMovers));
|
||||
|
||||
setInterval(() => {
|
||||
this.applyDeltasToFxData();
|
||||
|
||||
this.store.dispatch(fxDataUpdated(this.fxData));
|
||||
}, 1500)
|
||||
}, 1500);
|
||||
|
||||
setInterval(() => {
|
||||
this.calculateTopMovers();
|
||||
|
||||
this.store.dispatch(fxTopMoversUpdated(this.fxTopMovers));
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
calculateTopMovers() {
|
||||
let fxData = cloneDeep(this.fxData);
|
||||
fxData.sort((a, b) => {
|
||||
return Math.abs(b.pct_net_change) - Math.abs(a.pct_net_change);
|
||||
});
|
||||
|
||||
this.fxTopMovers = fxData.slice(0, 20);
|
||||
}
|
||||
|
||||
applyDeltasToFxData() {
|
||||
@@ -34,7 +53,7 @@ export default class {
|
||||
row['pct_net_change'] = multiplier * (this.random(30, 100) / 100).toFixed(2);
|
||||
|
||||
for (let symbolToUpdate of fxSymbolsToUpdate) {
|
||||
if(row[symbolToUpdate] === null) {
|
||||
if (row[symbolToUpdate] === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -42,7 +61,6 @@ export default class {
|
||||
row[symbolToUpdate] = (multiplier * Math.random()).toFixed(2);
|
||||
}
|
||||
}
|
||||
return this.fxData;
|
||||
}
|
||||
|
||||
random(min, max) {
|
||||
@@ -75,7 +93,8 @@ export default class {
|
||||
|
||||
rowData.push(row);
|
||||
}
|
||||
return rowData;
|
||||
|
||||
this.fxData = rowData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,251 +188,3 @@ const FX_CURRENCY_MATRIX = [
|
||||
["CHFJPY", "0.60", "0.96", "0.22", "0.52", "0.63", "0.24", "0.76", "0.91", "0.80", "-0.14", "0.59", "-0.01", "0.60", "-0.08", "0.63", "0.39", "0.30", "-0.71", "0.18", "0.94", "-0.30", "0.28", "0.87", "0.92", "-0.19", "0.18", "-0.40", "0.44", "0.88", "0.52", "0.50", "0.44", "0.84", "0.24", "0.09", "0.15", "0.10", "0.30", "-0.05", "0.79", null, "0.99"],
|
||||
["CHFCAD", "0.93", "0.24", "0.05", "0.40", "0.75", "0.12", "0.90", "0.56", "0.46", "0.62", "0.71", "0.01", "0.04", "0.02", "0.49", "0.51", "-0.94", "0.93", "0.80", "-0.40", "0.79", "0.82", "-0.04", "0.38", "-0.24", "-0.17", "0.78", "-0.42", "0.92", "-0.83", "-0.01", "0.54", "0.73", "0.52", "0.95", "0.44", "0.90", "0.81", "0.42", "0.26", "0.36", null]
|
||||
];
|
||||
|
||||
// for (i = 0; i < MAJOR_CURRENCIES.length; i++) {
|
||||
// for (j = 0; j < MAJOR_CURRENCIES.length; j++) {
|
||||
// if (i === j) {
|
||||
// continue;
|
||||
// }
|
||||
// console.log(`${MAJOR_CURRENCIES[i] + MAJOR_CURRENCIES[j]}`);
|
||||
// }
|
||||
// }
|
||||
|
||||
// let rows = [];
|
||||
// let row = ['Symbol'];
|
||||
//
|
||||
// rows = [];
|
||||
// for (j = 0; j < FX_CURRENCY_CODE.length; j++) {
|
||||
// row.push(FX_CURRENCY_CODE[j]);
|
||||
// }
|
||||
// rows.push(row);
|
||||
//
|
||||
// for (i = 0; i < FX_CURRENCY_CODE.length; i++) {
|
||||
// let row = [];
|
||||
// row.push(FX_CURRENCY_CODE[i]);
|
||||
//
|
||||
// for (j = 0; j < FX_CURRENCY_CODE.length; j++) {
|
||||
// if (i === j) {
|
||||
// row.push(null);
|
||||
// } else {
|
||||
// var mutliplier = ((Math.random() * 10) > 8) ? -1 : 1;
|
||||
// row.push((mutliplier * Math.random()).toFixed(2))
|
||||
// // row.push(`${FX_CURRENCY_CODE[j]} value`)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// rows.push(row);
|
||||
// }
|
||||
//
|
||||
// JSON.stringify(rows);
|
||||
//
|
||||
// const FX_CURRENCY_CODE = [
|
||||
// 'USDGBP',
|
||||
// 'USDEUR',
|
||||
// 'USDAED',
|
||||
// 'USDJPY',
|
||||
// 'USDCAD',
|
||||
// 'USDCHF',
|
||||
// 'GBPUSD',
|
||||
// 'GBPEUR',
|
||||
// 'GBPAED',
|
||||
// 'GBPJPY',
|
||||
// 'GBPCAD',
|
||||
// 'GBPCHF',
|
||||
// 'EURUSD',
|
||||
// 'EURGBP',
|
||||
// 'EURAED',
|
||||
// 'EURJPY',
|
||||
// 'EURCAD',
|
||||
// 'EURCHF',
|
||||
// 'AEDUSD',
|
||||
// 'AEDGBP',
|
||||
// 'AEDEUR',
|
||||
// 'AEDJPY',
|
||||
// 'AEDCAD',
|
||||
// 'AEDCHF',
|
||||
// 'JPYUSD',
|
||||
// 'JPYGBP',
|
||||
// 'JPYEUR',
|
||||
// 'JPYAED',
|
||||
// 'JPYCAD',
|
||||
// 'JPYCHF',
|
||||
// 'CADUSD',
|
||||
// 'CADGBP',
|
||||
// 'CADEUR',
|
||||
// 'CADAED',
|
||||
// 'CADJPY',
|
||||
// 'CADCHF',
|
||||
// 'CHFUSD',
|
||||
// 'CHFGBP',
|
||||
// 'CHFEUR',
|
||||
// 'CHFAED',
|
||||
// 'CHFJPY',
|
||||
// 'CHFCAD'
|
||||
// ];
|
||||
//
|
||||
// const MAJOR_CURRENCIES = [
|
||||
// 'USD',
|
||||
// 'GBP',
|
||||
// 'EUR',
|
||||
// 'AED',
|
||||
// 'JPY',
|
||||
// 'CAD',
|
||||
// 'CHF'];
|
||||
//
|
||||
// const SECONDARY_CURRENCIES = [
|
||||
// 'AFN',
|
||||
// 'ALL',
|
||||
// 'AMD',
|
||||
// 'ANG',
|
||||
// 'AOA',
|
||||
// 'ARS',
|
||||
// 'AUD',
|
||||
// 'AWG',
|
||||
// 'AZN',
|
||||
// 'BAM',
|
||||
// 'BBD',
|
||||
// 'BDT',
|
||||
// 'BGN',
|
||||
// 'BHD',
|
||||
// 'BIF',
|
||||
// 'BMD',
|
||||
// 'BND',
|
||||
// 'BOB',
|
||||
// 'BRL',
|
||||
// 'BSD',
|
||||
// 'BTN',
|
||||
// 'BWP',
|
||||
// 'BYN',
|
||||
// 'BZD',
|
||||
// 'CDF',
|
||||
// 'CLP',
|
||||
// 'CNY',
|
||||
// 'COP',
|
||||
// 'CRC',
|
||||
// 'CUC',
|
||||
// 'CUP',
|
||||
// 'CVE',
|
||||
// 'CZK',
|
||||
// 'DJF',
|
||||
// 'DKK',
|
||||
// 'DOP',
|
||||
// 'DZD',
|
||||
// 'EGP',
|
||||
// 'ERN',
|
||||
// 'ETB',
|
||||
// 'FJD',
|
||||
// 'FKP',
|
||||
// 'GEL',
|
||||
// 'GGP',
|
||||
// 'GHS',
|
||||
// 'GIP',
|
||||
// 'GMD',
|
||||
// 'GNF',
|
||||
// 'GTQ',
|
||||
// 'GYD',
|
||||
// 'HKD',
|
||||
// 'HNL',
|
||||
// 'HRK',
|
||||
// 'HTG',
|
||||
// 'HUF',
|
||||
// 'IDR',
|
||||
// 'ILS',
|
||||
// 'IMP',
|
||||
// 'INR',
|
||||
// 'IQD',
|
||||
// 'IRR',
|
||||
// 'ISK',
|
||||
// 'JEP',
|
||||
// 'JMD',
|
||||
// 'JOD',
|
||||
// 'KES',
|
||||
// 'KGS',
|
||||
// 'KHR',
|
||||
// 'KMF',
|
||||
// 'KPW',
|
||||
// 'KRW',
|
||||
// 'KWD',
|
||||
// 'KYD',
|
||||
// 'KZT',
|
||||
// 'LAK',
|
||||
// 'LBP',
|
||||
// 'LKR',
|
||||
// 'LRD',
|
||||
// 'LSL',
|
||||
// 'LYD',
|
||||
// 'MAD',
|
||||
// 'MDL',
|
||||
// 'MGA',
|
||||
// 'MKD',
|
||||
// 'MMK',
|
||||
// 'MNT',
|
||||
// 'MOP',
|
||||
// 'MRO',
|
||||
// 'MUR',
|
||||
// 'MVR',
|
||||
// 'MWK',
|
||||
// 'MXN',
|
||||
// 'MYR',
|
||||
// 'MZN',
|
||||
// 'NAD',
|
||||
// 'NGN',
|
||||
// 'NIO',
|
||||
// 'NOK',
|
||||
// 'NPR',
|
||||
// 'NZD',
|
||||
// 'OMR',
|
||||
// 'PAB',
|
||||
// 'PEN',
|
||||
// 'PGK',
|
||||
// 'PHP',
|
||||
// 'PKR',
|
||||
// 'PLN',
|
||||
// 'PYG',
|
||||
// 'QAR',
|
||||
// 'RON',
|
||||
// 'RSD',
|
||||
// 'RUB',
|
||||
// 'RWF',
|
||||
// 'SAR',
|
||||
// 'SBD',
|
||||
// 'SCR',
|
||||
// 'SDG',
|
||||
// 'SEK',
|
||||
// 'SGD',
|
||||
// 'SHP',
|
||||
// 'SLL',
|
||||
// 'SOS',
|
||||
// 'SPL',
|
||||
// 'SRD',
|
||||
// 'STD',
|
||||
// 'SVC',
|
||||
// 'SYP',
|
||||
// 'SZL',
|
||||
// 'THB',
|
||||
// 'TJS',
|
||||
// 'TMT',
|
||||
// 'TND',
|
||||
// 'TOP',
|
||||
// 'TRY',
|
||||
// 'TTD',
|
||||
// 'TVD',
|
||||
// 'TWD',
|
||||
// 'TZS',
|
||||
// 'UAH',
|
||||
// 'UGX',
|
||||
// 'UYU',
|
||||
// 'UZS',
|
||||
// 'VEF',
|
||||
// 'VND',
|
||||
// 'VUV',
|
||||
// 'WST',
|
||||
// 'XAF',
|
||||
// 'XCD',
|
||||
// 'XDR',
|
||||
// 'XOF',
|
||||
// 'XPF',
|
||||
// 'YER',
|
||||
// 'ZMW',
|
||||
// 'ZWD',
|
||||
// 'ZAR'];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {createStore} from "redux";
|
||||
|
||||
import fxData from "../reducers/fxData";
|
||||
import fxData from "../reducers/fxDataReducer.jsx";
|
||||
|
||||
class StoreService {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user