I have a project that contains several interfaces, and among these interfaces there is an interface to display a set of statistics,
I am using react-vis library.
But the problem is that I want to display the values as shown in the image:
I have one x, y, and y1
The problem is that I have multiple Y values
How can I solve this problem?
import { CSSProperties, FunctionComponent } from 'react';
import {
XYPlot,
XAxis,
YAxis,
VerticalGridLines,
HorizontalGridLines,
VerticalBarSeries,
} from 'react-vis';
import { BaseChart } from './data/interfaces';
interface BarChartProps extends BaseChart {
colorValue?: string;
color?: string;
style?: CSSProperties;
barWidth?: number;
stroke?: string;
fill?: string;
}
const BarChart: FunctionComponent<BarChartProps> = ({
colorRange,
colorValue,
color,
data,
style,
barWidth,
width,
height,
stroke,
fill,
}) => {
console.log('datadfdfdf: ', data);
var yValues: any = data?.map((y, index) => {
console.log('ytr: ', y);
return y?.y;
})
var y1Values: any = data?.map((y1, index) => {
console.log('ytr1: ', y1?.y1);
return y1.y1;
})
console.log('yValues: ', yValues);
return (
<>
<XYPlot
margin={{ bottom: 30, left: 20, top: 15 }}
xType='ordinal'
width={width?width:450}
height={height}
>
<VerticalGridLines marginLeft={2} width={5} />
<HorizontalGridLines tickValues={yValues} />
<HorizontalGridLines tickValues={y1Values} />
<XAxis />
<YAxis tickValues={y1Values}
tickSize={12}/>
<VerticalBarSeries
_colorValue={colorValue ? colorValue : 'red'}
colorRange={
colorRange
? colorRange
: ['#005fff36', '#00800045', '#fafafa']
}
barWidth={barWidth ? barWidth : 0.3}
color={color ? color : 'yellow'}
fill={fill ? fill : '#C6E2DD'}
stroke={stroke ? stroke : '#55805045'}
width={6}
style={style}
data={data}
/>
</XYPlot>
</>
);
};
export default BarChart;
I solve my problem by update the code, the updated code from "the part that i added it" comment, just i duplicate YAxis and VerticalBarSeries:
import { CSSProperties, FunctionComponent, useEffect, useState } from 'react';
import {
XYPlot,
XAxis,
YAxis,
VerticalGridLines,
HorizontalGridLines,
VerticalBarSeries,
} from 'react-vis';
import { BaseChart } from '../data/interfaces';
interface BarChartProps extends BaseChart {
colorValue?: string;
color?: string;
style?: CSSProperties;
barWidth?: number;
stroke?: string;
fill?: string;
}
const BarChartMultiY: FunctionComponent<BarChartProps> = ({
colorRange,
colorValue,
color,
data,
style,
barWidth,
width,
height,
stroke,
fill,
}) => {
var yValues: any = data?.map((y, index) => {
return y?.y;
})
var y1Values: any = data?.map((y1, index) => {
return y1?.y1;
})
var topography: any[] = [];
const [xtopography, setXTopography] = useState<any[]>([])
useEffect(() => {
data?.map((xy, index) => {
let x: any = xy?.x;
let y: any = xy?.y1;
let xyData: any = { x: x, y: y }
topography.push(xyData);
setXTopography(topography)
return topography;
})
}, [data])
return (
<>
<XYPlot
margin={{ bottom: 30, left: 20, top: 20 }}
xType='ordinal'
width={width ? width : 450}
height={height}
>
<VerticalGridLines marginLeft={2} width={5} />
<HorizontalGridLines tickValues={yValues} />
<XAxis />
<YAxis tickValues={yValues}
tickSize={12} />
<VerticalBarSeries
_colorValue={colorValue ? colorValue : 'red'}
colorRange={
colorRange
? colorRange
: ['#005fff36', '#00800045', '#fafafa']
}
barWidth={barWidth ? barWidth : 0.3}
color={color ? color : 'yellow'}
fill={fill ? fill : '#C6E2DD'}
stroke={stroke ? stroke : '#55805045'}
width={6}
style={style}
data={data}
/>
{/* /// the part that i added it */}
<YAxis tickValues={y1Values}
tickSize={12} />
<VerticalBarSeries
_colorValue={colorValue ? colorValue : 'red'}
colorRange={
colorRange
? colorRange
: ['#005fff36', '#00800045', '#fafafa']
}
barWidth={barWidth ? barWidth : 0.3}
color={color ? color : 'yellow'}
fill={'#96DED1'}
stroke={stroke ? stroke : '#55805045'}
width={6}
style={style}
data={xtopography}
/>
</XYPlot>
</>
);
};
export default BarChartMultiY;
and this link help me also:
enter link description here
Related
I have a project, and this project contains several interfaces, and among these interfaces there is an interface to display a set of statistics, meaning that the interface displays a set of charts,
And I use a library called:
react-vis
But the problem is shown in this picture:
When the numbers increase, the numbers are displayed on top of each other:
How can height be dynamic?
import ReactChart from '../../common/chart';
<Row className='mt-8'>
<Col lg={24}>
<ReactChart
type={typeChart.Bar}
data={xtopography}
height={240}
width={1000}
fill={'#1E8D77'}
/>
</Col>
</Row>
BarChart.tsx:
import { CSSProperties, FunctionComponent, useEffect, useState } from 'react';
import {
XYPlot,
XAxis,
YAxis,
VerticalGridLines,
HorizontalGridLines,
VerticalBarSeries,
makeHeightFlexible
} from 'react-vis';
import { BaseChart } from './data/interfaces';
interface BarChartProps extends BaseChart {
colorValue?: string;
color?: string;
style?: CSSProperties;
barWidth?: number;
stroke?: string;
fill?: string;
}
const BarChart: FunctionComponent<BarChartProps> = ({
colorRange,
colorValue,
color,
data,
style,
barWidth,
width,
height,
stroke,
fill,
}) => {
var yValues: any = data?.map((y, index) => {
return y?.y;
})
const FlexibleXYPlot = makeHeightFlexible(yValues);
return (
<>
<XYPlot
margin={{ bottom: 30, left: 20, top: 20 }}
xType='ordinal'
width={width ? width : 450}
height={height}
>
<VerticalGridLines marginLeft={2} width={5} />
<HorizontalGridLines tickValues={yValues} />
<XAxis />
<YAxis tickValues={yValues}
tickSize={12} />
<VerticalBarSeries
_colorValue={colorValue ? colorValue : 'red'}
colorRange={
colorRange
? colorRange
: ['#005fff36', '#00800045', '#fafafa']
}
barWidth={barWidth ? barWidth : 0.3}
color={color ? color : 'yellow'}
fill={fill ? fill : '#C6E2DD'}
stroke={stroke ? stroke : '#55805045'}
width={6}
style={style}
data={data}
/>
</XYPlot>
</>
);
};
export default BarChart;
I am trying to display a dot that moves on the line when I use the slider (SliderWidget.js) but nothing is happening. I did the console.log() to see if it works and it does but on the chart (I am using Recharts library) nothing happens.
import SliderWidget from "../SliderWidget";
import { LineChart, Line, XAxis, YAxis, CartesianGrid } from "recharts";
import { useState } from "react";
const LongitudinalCoG = () => {
const [chartValue, setChartValue] = useState([
{
data: [
{ x: 1000, y: 2000 },
{ x: 3000, y: 3500 },
{ x: 4000, y: 4500 },
{ x: 4100, y: 4200 },
],
},
{
//this is the little dot on the chart
data: [{ x: 2000, y: 2500 }],
},
]);
console.log(chartValue);
function sliderData(newValue) {
chartValue[1].data[0].x = newValue;
setChartValue(chartValue);
console.log(chartValue);
}
return (
<div>
<LineChart
width={500}
height={300}
data={chartValue}
margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="x" />
<YAxis dataKey="y" />
<Line
dataKey="y"
data={chartValue[0].data}
stroke="white"
dot={{
r: 1,
}}
/>
<Line
dataKey="y"
data={chartValue[1].data}
stroke="blue"
dot={{
r: 3,
}}
/>
</LineChart>
<SliderWidget
minValue={chartValue[1].data[0].x}
maxValue={chartValue[1].data[0].y}
sliderTitle={"Override (mm)"}
onValueChanged={sliderData}
/>
</div>
);
};
export default LongitudinalCoG;
this is the SliderWidget component
import { useState } from "react";
import { Slider, InputNumber } from "antd";
import Icon from "#mdi/react";
import { mdiPlus, mdiMinus } from "#mdi/js";
import "../styles/Expanded.css";
const SliderWidget = (props) => {
const [inputValue, setInputValue] = useState(0);
const onChange = (newValue) => {
setInputValue(newValue);
props.onValueChanged(newValue);
};
function increment() {
if (inputValue + 1 < props.maxValue) {
setInputValue(inputValue + 1);
}
}
function decrement() {
if (inputValue - 1 > props.minValue) {
setInputValue(inputValue - 1);
}
}
return (
<div>
<div className="slider-title">
{props.sliderTitle}
<InputNumber
className="input-number-helicopter"
style={{ width: "40px", marginBottom: "10px", marginTop: "10px" }}
value={inputValue}
onChange={onChange}
controls={false}
/>
<div className="increment-decrement-container">
<button className="increment-button" onClick={increment}>
<Icon path={mdiPlus} size={"1rem"} color={"#FFFFFF"} />
</button>
<button className="decrement-button" onClick={decrement}>
<Icon path={mdiMinus} size={"1rem"} color={"#FFFFFF"} />
</button>
</div>
</div>
<div>
<Slider
className="slider-helicopter"
minValue={1}
maxValue={4000}
onChange={onChange}
value={inputValue}
/>
</div>
</div>
);
};
export default SliderWidget;
This is the chart. I want to make the litle dot move on the white lines when I use the slider or the increment/decrement buttons
As I said, I run the console.log() and it is working but it is not showing on the chart!
We have the following Panel to manually add/delete choices inside a SharePoint column named Category:-
and here is the related .tsx code for the above Panel:-
import * as React from "react";
import {
Stack,
ProgressIndicator,
Panel,
PanelType,
DefaultButton,
AnimationStyles,
mergeStyles,
TextField,
PrimaryButton,
Dropdown,
IDropdownOption,
MessageBar,
MessageBarType,
Label,
Text,
ILabelStyles,
Link,
IconButton,
} from "office-ui-fabric-react";
import { _copyAndSort } from "../../controls/helpers";
import * as moment from "moment";
import * as strings from "DocumentsViewerWebPartStrings";
import { IReadonlyTheme } from "#microsoft/sp-component-base";
import Dropzone from "../../controls/DropzoneExport";
import { IDocument } from "../../models/IDocument";
export interface ICategoriesPanelProps {
themeVariant: IReadonlyTheme | undefined;
showPanel: boolean;
hidePanel: () => void;
categories: string[];
addCategory: (category: string) => void;
removeCategory: (category: string) => void;
castFiletoIDoc: (file: File) => IDocument;
}
export interface ICategoriesPanelState {
busy: boolean;
newCategory: string;
uploadPlaceholders: IDocument[];
}
export default class CategoriesPanel extends React.Component<ICategoriesPanelProps, ICategoriesPanelState> {
constructor(props: ICategoriesPanelProps) {
super(props);
this.state = { busy: true, newCategory: null ,uploadPlaceholders: []};
}
public componentDidMount(): void {
this.setState({ busy: false });
}
private handleNewCategoryFieldChange = (e, newValue: string) => {
this.setState({ newCategory: newValue });
};
private add = async () => {
this.setState({ busy: true });
await this.props.addCategory(this.state.newCategory);
this.setState({ busy: false, newCategory: null });
};
private remove = async (category: string) => {
this.setState({ busy: true });
if (category) {
this.props.removeCategory(category);
}
this.setState({ busy: false });
};
private onDrop = (moreFiles) => {
const placeholders = [...this.state.uploadPlaceholders];
moreFiles.forEach((file, i) => {
const idoc = this.props.castFiletoIDoc(file);
placeholders.push({
...idoc,
key: i.toString(),
});
});
this.setState({ uploadPlaceholders: [...placeholders] });
// Upload the file
//this.props.uploadFolderIcon(moreFiles[0], this.props.folder);
};
private removeDocument = (document: IDocument) => {
this.setState({ uploadPlaceholders: [] });
};
public render(): React.ReactElement<ICategoriesPanelProps> {
const appearingStyle = mergeStyles(AnimationStyles.scaleDownIn100);
return (
<Panel
headerText={strings.ManageCategories}
type={PanelType.medium}
isOpen={this.props.showPanel}
onDismiss={this.props.hidePanel}
// You MUST provide this prop! Otherwise screen readers will just say "button" with no label.
closeButtonAriaLabel={strings.Close}
isBlocking={true}
hasCloseButton={true}
>
<Stack tokens={{ childrenGap: 15 }}>
<Stack.Item>
<Dropzone
themeVariant={this.props.themeVariant}
onDrop={this.onDrop}
uploadPlaceholders={this.state.uploadPlaceholders}
removeDocument={this.removeDocument}
/>
{/* <PrimaryButton
text={strings.StartUpload}
onClick={this.uploadDocuments}
disabled={this.state.uploading || this.state.uploadFiles.length === 0}
/> */}
</Stack.Item>
<Stack.Item align="end">
{this.props.categories.length} {strings.Categories.toLowerCase()}
</Stack.Item>
<Stack.Item>
<Stack tokens={{ childrenGap: 24 }}>
<Stack.Item
styles={{
root: {
padding: "10px 20px",
backgroundColor: this.props.themeVariant.palette.neutralLight,
},
}}
>
<Stack tokens={{ childrenGap: 4 }}>
<Stack.Item>
{this.props.categories.map((category, i) => (
<Stack
tokens={{ childrenGap: 6 }}
horizontal
horizontalAlign="space-between"
styles={{
root: {
alignItems: "center",
},
}}
className={appearingStyle}
>
<Stack.Item>{category}</Stack.Item>
<IconButton
iconProps={{ iconName: "Delete" }}
title={`${strings.Remove} ${category}`}
onClick={() => this.remove(category)}
disabled={this.state.busy}
/>
</Stack>
))}
</Stack.Item>
<Stack.Item>
<Stack
tokens={{ childrenGap: 6 }}
horizontal
horizontalAlign="space-between"
styles={{
root: {
alignItems: "center",
},
}}
className={appearingStyle}
>
<Stack.Item>
<TextField
label={strings.AddNewCategory}
name="newCategory"
value={this.state.newCategory}
onChange={this.handleNewCategoryFieldChange}
disabled={this.state.busy}
styles={{ root: { width: 300 } }}
/>
</Stack.Item>
<IconButton
iconProps={{ iconName: "Add" }}
title={`${strings.Add} ${this.state.newCategory}`}
onClick={this.add}
disabled={this.state.busy}
/>
</Stack>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Panel>
);
}
}
currently the SPFx allow to manually add/edit the choices, but my question is how we can read the uploaded excel sheet file (which will contain the choices) inside the DropZone, loop through the choices >> remove existing choices and add the ones inside the sheet? Can anyone advice please?
Here is the DropZoneExport.tsx:-
import * as React from "react";
import { Stack, IStyle } from "office-ui-fabric-react";
import { IReadonlyTheme } from "#microsoft/sp-component-base";
import * as strings from "DocumentsViewerWebPartStrings";
import { IDocument } from "../models/IDocument";
import DocumentRow from "./DocumentRow";
import { useCallback, useState } from "react";
import { useDropzone } from "react-dropzone";
export interface IDropzoneExportProps {
themeVariant: IReadonlyTheme | undefined;
onDrop: (files) => void;
uploadPlaceholders: IDocument[];
removeDocument: (document: IDocument) => void;
}
export interface IDocumentsDropzoneExportState {
files: any[];
}
export default function DropzoneExport(props: IDropzoneExportProps) {
// https://www.npmjs.com/package/react-dropzone
const onDrop = useCallback(async (acceptedFiles) => {
// Do something with the files
console.log("something dropped");
props.onDrop(acceptedFiles);
}, []);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
maxFiles: 1,
accept: {
"text/csv*": [".csv"],
//acceptedFiles={[".csv, text/csv, application/vnd.ms-excel, application/csv, text/x-csv, application/x-csv, text/comma-separated-values, text/x-comma-separated-values"]}
},
});
const dropBoxStyle: IStyle = {
border: "1px dashed",
borderColor: props.themeVariant.semanticColors.inputBorder,
padding: "0.5rem 1rem",
marginBottom: ".5rem",
backgroundColor: props.themeVariant.palette.neutralQuaternary,
};
return (
<Stack>
<Stack.Item styles={{ root: dropBoxStyle }}>
<div {...getRootProps()} style={{ outline: "none" }}>
<input {...getInputProps()} />
{isDragActive ? <p>{strings.Item_DropHere}</p> : <p>{strings.Item_DropInfo}</p>}
<div
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
{props.uploadPlaceholders.map((placeholder) => {
return <DocumentRow document={placeholder} themeVariant={props.themeVariant} removeDocument={props.removeDocument} />;
})}
</div>
</div>
</Stack.Item>
</Stack>
);
}
You can do that, but you may need to use a third-party library to read the excel sheet in the browser. A common solution for that is sheetjs library. There are no built-in helpers in the SPFx framework to parse Excel files, as far as I know.
But you should be able to install sheetjs using npm and then use it by import.
I am using React.createContext in order to perform some recording logic but it seems that I get an error reorder is not defined. I can't figure out why seems I have the function defined in my context, here is the code:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Grid, GridColumn as Column } from '#progress/kendo-react-grid';
import { DragAndDrop } from '#progress/kendo-react-common';
import products from './products.json';
import { Product } from './interfaces';
const ReorderContext = React.createContext<{
reorder: (dataItem: Product, direction: 'before' | 'after' | null) => void;
dragStart: (dataItem: Product) => void;
dragEnd: (dataItem?: Product) => void;
}>({ reorder: () => {}, dragStart: () => {}, dragEnd: () => {} });
const SelectionContext = React.createContext<[Product[], any]>([[], () => {}]);
const IsSelectedContext = React.createContext<boolean>(false);
const DragHintContext =
React.createContext<React.RefObject<HTMLElement> | null>(null);
const GridContext = React.createContext<React.RefObject<Grid> | null>(null);
const grid = React.createRef(null);
const hint = React.createRef();
null;
class App extends React.Component {
state = {
gridData: products,
selection: [],
activeItem: null,
};
reorder(dataItem: any, direction: 'before' | 'after') {
if (this.state.activeItem === dataItem) {
return;
}
let reorderedData = this.state.gridData.slice();
reorderedData = reorderedData.filter(
(item) =>
!this.state.selection.some(
(selectedItem) => selectedItem.ProductID === item.ProductID
)
);
let nextIndex = reorderedData.findIndex((p) => p === dataItem);
reorderedData.splice(
Math.max(nextIndex + (direction === 'before' ? 0 : 1), 0),
0,
...this.state.selection
);
this.setState({ gridData: reorderedData });
}
dragStart(dataItem: any) {
setActiveItem(dataItem);
}
dragEnd() {
setActiveItem(null);
}
render() {
return (
<GridContext.Provider value={grid}>
<DragHintContext.Provider value={hint}>
<ReorderContext.Provider value={{ reorder, dragStart, dragEnd }}>
<SelectionContext.Provider
value={[this.state.selection, setSelection]}
>
<DragAndDrop>
<Grid
ref={grid}
style={{ height: '400px' }}
data={this.state.gridData}
dataItemKey={'ProductID'}
rowRender={(row, rowProps) => (
<DraggableRow elementProps={row.props} {...rowProps} />
)}
>
<Column title="" width="80px" cell={SelectionCell} />
<Column field="ProductID" title="ID" width="60px" />
<Column field="ProductName" title="Name" width="200px" />
<Column field="Category.CategoryName" title="CategoryName" />
<Column field="UnitPrice" title="Price" width="80px" />
<Column field="UnitsInStock" title="In stock" width="80px" />
</Grid>
<DragHint
ref={hint}
portal={grid}
className="k-card"
style={{
display: this.state.activeItem ? undefined : 'none',
}}
>
{this.state.activeItem && this.state.activeItem.ProductName}
{this.state.selection.length > 1 && (
<div
style={{
position: 'absolute',
pointerEvents: 'none',
bottom: 0,
right: 0,
background: 'red',
padding: 8,
width: 32,
color: 'white',
borderRadius: '50%',
transform: 'translate(50%, 50%)',
}}
>
+{this.state.selection.length - 1}
</div>
)}
</DragHint>
</DragAndDrop>
</SelectionContext.Provider>
</ReorderContext.Provider>
</DragHintContext.Provider>
</GridContext.Provider>
);
}
}
ReactDOM.render(<App />, document.querySelector('my-app'));
What am I doing wrong here? What is the correct way to use the reorder value once it has been declared in the React.createContext
I am creating timer component and implement with every task. So when I start my timer for a single task then other task timer will be disabled or hidden. I am trying to disable other timer component on start timer but it gives me only value for current component. So how can I update all components when I start a single timer?
DeveloperTasks.js
import { Mutation, Query } from "react-apollo";
import gql from "graphql-tag";
import React, { Component, Fragment } from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import TaskTimer from "./TaskTimer";
import Note from "./Note";
import getCDTime from "../util/commonfunc";
import Button from "#material-ui/core/Button";
import IconButton from "#material-ui/core/IconButton";
import Paper from "#material-ui/core/Paper";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
import CircularProgress from "#material-ui/core/CircularProgress";
import Avatar from "#material-ui/core/Avatar";
import FormControl from "#material-ui/core/FormControl";
import InputLabel from "#material-ui/core/InputLabel";
import Select from "#material-ui/core/Select";
import MenuItem from "#material-ui/core/MenuItem";
import TextField from "#material-ui/core/TextField";
import Dialog from "#material-ui/core/Dialog";
import MuiDialogTitle from "#material-ui/core/DialogTitle";
import MuiDialogContent from "#material-ui/core/DialogContent";
import MuiDialogActions from "#material-ui/core/DialogActions";
import Popover from "#material-ui/core/Popover";
import DeleteIcon from "#material-ui/icons/Delete";
import AssignmentIcon from "#material-ui/icons/Assignment";
import NotesIcon from "#material-ui/icons/EventNote";
import AssignmentInd from "#material-ui/icons/AssignmentInd";
import CheckCircleOutline from "#material-ui/icons/CheckCircleOutline";
import CheckCircle from "#material-ui/icons/CheckCircle";
import CloseIcon from "#material-ui/icons/Close";
import Typography from "#material-ui/core/Typography";
import EditIcon from "#material-ui/icons/Edit";
import DateFnsUtils from "#date-io/date-fns";
import {
MuiPickersUtilsProvider,
TimePicker,
DatePicker
} from "material-ui-pickers";
import UserList from "../components/UserList";
import emails from "../components/UserList";
import TodoInlineForm from "../components/TodoInlineForm";
const ms = require("pretty-ms");
//Kanban Quearies
export const tasksQuery = gql`
query Developertasklist($contact_id_c: String) {
Developertasklist(contact_id_c: $contact_id_c) {
id
name
due_date
dtask_start_time
time_tracking_flag
dtask_total_time
status
}
}
`;
//Delete Task Mutation
export const DELETE_TODO = gql`
mutation todo_operations($id: String, $deleted: String) {
todo_operations(id: $id, deleted: $deleted) {
id
}
}
`;
//Complete Task Mutation
const COMPLETE_TASK_OPERATIONS = gql`
mutation todo_operations(
$id: String
$status: String
$actual_due_date: String
) {
todo_operations(
id: $id
status: $status
actual_due_date: $actual_due_date
) {
id
}
}
`;
const styles = theme => ({
root: {
width: "100%",
marginTop: theme.spacing(3),
overflowX: "auto"
},
icon: {
margin: theme.spacing.unit,
fontSize: 20
},
button: {
margin: theme.spacing.unit
},
listroot: {
width: "100%",
minWidth: 900,
backgroundColor: theme.palette.background.paper
},
tasklist: {
marginTop: 30
},
taskwidth: {
width: "55%",
display: "inline-flex"
},
timerwidth: {
width: "25%"
},
width5: {
width: "5%"
},
margin: {
margin: theme.spacing.unit
},
input: {
display: "none"
},
datepadding: {
"padding-right": "10px;",
width: "17%"
},
formControl: {
minWidth: 120
},
elementpadding: {
"padding-right": "10px;"
},
completeIcon: {
color: "Green"
},
popover: {
pointerEvents: "none"
},
label: {
display: "inline",
padding: ".2em .6em .3em",
"font-size": "75%",
"font-weight": "700",
"line-height": 1,
color: "#fff",
"text-align": "center",
"white-space": "nowrap",
"vertical-align": "baseline",
"border-radius": ".25em"
},
labelcomplete: {
"background-color": "#5cb85c"
},
labelprogress: {
"background-color": "#5bc0de"
},
labelonhold: {
"background-color": "#d9534f"
},
labelqafail: {
"background-color": "#d9534f"
},
labelnotstated: {
"background-color": "#777"
},
labelqa: {
"background-color": "#337ab7"
},
labelqapassed: {
"background-color": "#777"
},
labeldefered: {
"background-color": "#f0ad4e"
},
hideelement: {
display: "none"
},
showelement: {
display: "block"
}
});
const DialogTitle = withStyles(theme => ({
root: {
borderBottom: `1px solid ${theme.palette.divider}`,
margin: 0,
padding: theme.spacing.unit * 2
},
closeButton: {
position: "absolute",
right: theme.spacing.unit,
top: theme.spacing.unit,
color: theme.palette.grey[500]
}
}))(props => {
const { children, classes, onClose } = props;
return (
<MuiDialogTitle disableTypography className={classes.root}>
<Typography variant="h6">{children}</Typography>
{onClose ? (
<IconButton
aria-label="Close"
className={classes.closeButton}
onClick={onClose}
>
<CloseIcon />
</IconButton>
) : null}
</MuiDialogTitle>
);
});
const DialogContent = withStyles(theme => ({
root: {
margin: 0,
padding: theme.spacing.unit * 2
}
}))(MuiDialogContent);
const DialogActions = withStyles(theme => ({
root: {
borderTop: `1px solid ${theme.palette.divider}`,
margin: 0,
padding: theme.spacing.unit
}
}))(MuiDialogActions);
class DeveloperTasks extends React.Component {
state = {
start_date: new Date(),
end_date: new Date(),
status: "",
task: "",
searchTerm: "",
open: false,
anchorEl: null,
selectedValue: emails[1],
openreport: false,
openTodoForm: false,
taskid: ""
};
constructor(props) {
super(props);
this.searchUpdated = this.searchUpdated.bind(this);
}
handleDateChange = name => date => {
this.setState({ [name]: date });
};
handleChange = name => event => {
this.setState({ [name]: event.target.value });
};
handleClickOpen = name => event => {
this.setState({
open: true
});
};
handleClose = () => {
this.setState({ open: false });
};
handleClickDialogOpen = () => {
this.setState({ openreport: true });
};
handleDialogClose = value => {
this.setState({ selectedValue: value, openreport: false });
};
searchUpdated(term) {
this.setState({ searchTerm: term });
}
handlePopoverOpen = event => {
this.setState({ anchorEl: event.currentTarget });
};
handlePopoverClose = () => {
this.setState({ anchorEl: null });
};
handleClickTodoOpen(taskid) {
this.setState({ taskid: taskid, openTodoForm: true });
}
componentWillReceiveProps(newProps) {
this.setState({ openTodoForm: newProps.open });
}
render() {
let todoinlineform = "";
const { classes, contact_id } = this.props;
const { anchorEl } = this.state;
const open = Boolean(anchorEl);
let currdatetime = getCDTime.getCurrentDateTime();
let shownbutton = {
display: "block"
};
if (
this.state.openTodoForm &&
this.state.openTodoForm === true &&
this.state.taskid != ""
) {
todoinlineform = (
<TodoInlineForm
open={this.state.openTodoForm}
taskid={this.state.taskid}
modaltitle="Edit Todo"
/>
);
}
return contact_id === "" ? (
""
) : (
<Query query={tasksQuery} variables={{ contact_id_c: contact_id }}>
{({ loading, error, data: { Developertasklist } }) => {
if (error) return <p>{error}</p>;
if (loading) return <CircularProgress className={classes.progress} />;
//Filter with task name
if (this.state.task && this.state.task != "") {
Developertasklist = Developertasklist.filter(
developertasklist =>
developertasklist.name
.toLowerCase()
.indexOf(this.state.task.toLowerCase()) != -1
);
}
//Task status wise filter
if (this.state.status && this.state.status != "") {
Developertasklist = Developertasklist.filter(
developertasklist => developertasklist.status == this.state.status
);
}
//Label array for apply class on status label
let labelcolor = [
{ status: "In Progress", class: classes.labelprogress },
{ status: "Completed", class: classes.labelcomplete },
{ status: "On Hold", class: classes.labelonhold },
{ status: "QA Fail", class: classes.labelqafail },
{ status: "Not Started", class: classes.labelnotstated },
{ status: "QA", class: classes.labelqa },
{ status: "QA Passed", class: classes.labelqapassed },
{ status: "Deferred", class: classes.labeldefered }
];
return (
<Fragment>
<br />
<div className={classes.tasklist}>
<div className="picker">
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<DatePicker
label="Start Date"
name="start_date"
value={this.state.start_date}
format="yyyy-MM-dd"
onChange={this.handleDateChange("start_date")}
className={classes.datepadding}
animateYearScrolling
/>
<DatePicker
label="End Date"
name="end_date"
value={this.state.end_date}
format="yyyy-MM-dd"
onChange={this.handleDateChange("end_date")}
className={classes.datepadding}
animateYearScrolling
/>
</MuiPickersUtilsProvider>
<Button
type="submit"
variant="contained"
color="primary"
className={classes.button}
>
Search
</Button>
<Button
variant="contained"
color="secondary"
className={classes.button}
>
Reset
</Button>
</div>
<FormControl className={classes.formControl}>
<InputLabel htmlFor="status-simple">Status</InputLabel>
<Select
value={this.state.status}
onChange={this.handleChange("status")}
className={classes.elementpadding}
inputProps={{
name: "status",
id: "status"
}}
>
<MenuItem value="">
<em>Please Select</em>
</MenuItem>
<MenuItem value="Not Started">Not Started</MenuItem>
<MenuItem value="In Progress">In Progress</MenuItem>
<MenuItem value="On Hold">On Hold</MenuItem>
<MenuItem value="Deferred">Deferred</MenuItem>
<MenuItem value="Completed">Completed</MenuItem>
<MenuItem value="QA">QA</MenuItem>
<MenuItem value="QA Passed">QA Passed</MenuItem>
<MenuItem value="QA Fail">QA Fail</MenuItem>
</Select>
</FormControl>
<TextField
id="standard-name"
label="Task"
className={classes.textField}
value={this.state.task}
onChange={this.handleChange("task")}
/>
</div>
<div className={classes.tasklist}>
<Paper className={classes.listroot}>
<List className={classes.listroot}>
{Developertasklist.map((task, index) => {
let statusLabel = labelcolor.filter(
obj => obj.status == task.status
);
let completeStatusClass = classes.hideelement;
let pendingStatusClass = "";
let hidetimer = "";
if (task.status === "Completed") {
pendingStatusClass = classes.hideelement;
hidetimer = "hide";
completeStatusClass = "";
}
if (statusLabel.length > 0)
statusLabel = statusLabel[0].class;
return (
<ListItem key={index} divider="true">
<div className={classes.taskwidth}>
<Avatar>
<AssignmentIcon />
</Avatar>
<ListItemText
primary={
<React.Fragment>
{task.name} - {task.due_date}
</React.Fragment>
}
secondary={
<React.Fragment>
<Typography
component="span"
className={[classes.label, statusLabel]}
color="textPrimary"
>
{task.status}
</Typography>
</React.Fragment>
}
/>
</div>
<div className={classes.timerwidth}>
<div>
<TaskTimer
developerlist={task}
hidetimer={hidetimer}
/>
</div>
</div>
<div className={classes.width5}>
<EditIcon
onClick={event => {
this.handleClickTodoOpen(task.id);
}}
/>
</div>
<div className={classes.width5}>
<Mutation mutation={COMPLETE_TASK_OPERATIONS}>
{(todo_operations, { loading, error }) => (
<CheckCircleOutline
className={pendingStatusClass}
aria-owns={
open ? "mouse-over-popover" : undefined
}
aria-haspopup="true"
onMouseEnter={this.handlePopoverOpen}
onMouseLeave={this.handlePopoverClose}
onClick={event => {
todo_operations({
variables: {
id: task.id,
actual_due_date: currdatetime,
status: "Completed"
}
});
}}
/>
)}
</Mutation>
<Popover
id="mouse-over-popover"
className={classes.popover}
classes={{
paper: classes.paper
}}
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
onClose={this.handlePopoverClose}
disableRestoreFocus
>
<Typography>Mark as completed.</Typography>
</Popover>
<CheckCircle
className={[
classes.completeIcon,
completeStatusClass
]}
/>
</div>
<div className={classes.width5}>
<div className={pendingStatusClass}>
{/* <Typography variant="subtitle1">
Selected: {this.state.selectedValue}
</Typography> */}
<AssignmentInd
onClick={this.handleClickDialogOpen}
/>
<UserList
selectedValue={this.state.selectedValue}
open={this.state.openreport}
onClose={this.handleDialogClose}
/>
</div>
</div>
<div className={classes.width5}>
<NotesIcon onClick={this.handleClickOpen()} />
<Dialog
onClose={this.handleClose}
aria-labelledby="customized-dialog-title"
open={this.state.open}
>
<DialogTitle
id="customized-dialog-title"
onClose={this.handleClose}
>
Notes
</DialogTitle>
<DialogContent>
<Note />
</DialogContent>
</Dialog>
</div>
<div className={classes.width5}>
<Mutation mutation={DELETE_TODO}>
{(todo_operations, { loading, error }) => (
<DeleteIcon
aria-label="Delete"
onClick={event => {
todo_operations({
variables: {
id: task.id,
deleted: "1"
}
});
}}
/>
)}
</Mutation>
</div>
</ListItem>
);
})}
</List>
</Paper>
</div>
{todoinlineform}
</Fragment>
);
}}
</Query>
);
}
}
export default withStyles(styles, { withTheme: true })(DeveloperTasks);
TaskTimer.js
import { Mutation, Query } from "react-apollo";
import gql from "graphql-tag";
const React = require("react");
const ms = require("pretty-ms");
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import StartIcon from "#material-ui/icons/PlayCircleFilled";
import StopIcon from "#material-ui/icons/Stop";
import getCDTime from "../util/commonfunc";
//Start timer mutation
const TODO_OPERATIONS = gql`
mutation todo_operations(
$id: String
$status: String
$dtask_start_time: String
$time_tracking_flag: String
$developer_daily_hours: String
$is_task_started: String
$actual_start_date: String
) {
todo_operations(
id: $id
status: $status
dtask_start_time: $dtask_start_time
time_tracking_flag: $time_tracking_flag
developer_daily_hours: $developer_daily_hours
is_task_started: $is_task_started
actual_start_date: $actual_start_date
) {
id
}
}
`;
//Stop timer mutation
const STOP_TIMER = gql`
mutation todo_operations(
$id: String
$dtask_stop_time: String
$dtask_total_time: String
$time_tracking_flag: String
) {
todo_operations(
id: $id
dtask_stop_time: $dtask_stop_time
dtask_total_time: $dtask_total_time
time_tracking_flag: $time_tracking_flag
) {
id
}
}
`;
const styles = theme => ({
button: {
margin: theme.spacing.unit
},
stopbutton: {
margin: theme.spacing.unit,
color: "Red"
},
input: {
display: "none"
},
clock: {
color: "Green",
fontWeight: "700",
fontSize: "15px"
},
hideelement: {
display: "none"
},
timerClass: {
display: "none"
}
});
class TaskTimer extends React.Component {
constructor(props) {
const total_time = !props.developerlist.dtask_total_time
? parseInt(0)
: parseInt(props.developerlist.dtask_total_time);
let statetime = total_time;
let stateison = false;
super(props);
if (props.developerlist.time_tracking_flag === "yes") {
let currentdatetime = new Date(getCDTime.getCurrentDateTime());
let start_time = new Date(props.developerlist.dtask_start_time);
let time_diff = Math.abs(currentdatetime - start_time) / 1000;
statetime = time_diff + total_time;
stateison = true;
this.state = {
time: statetime,
isOn: stateison
};
this.startTimer();
}
this.state = {
time: statetime,
isOn: stateison,
timerClass: "test"
};
this.startTimer = this.startTimer.bind(this);
this.stopTimer = this.stopTimer.bind(this);
}
startTimer(next) {
this.setState({
isOn: true,
time: this.state.time,
timerClass: "abc"
});
this.timer = setInterval(
() =>
this.setState({
time: parseInt(this.state.time + 1)
}),
1000
);
}
stopTimer() {
this.state.time = parseInt(this.state.time);
this.setState({ isOn: false });
clearInterval(this.timer);
}
render() {
let totalTaskTime = parseInt(this.state.time) * 1000;
const { classes, theme } = this.props;
let hideTimerClass =
this.props.hidetimer === "hide" ? classes.hideelement : "";
let currdatetime = getCDTime.getCurrentDateTime();
let start =
(this.state.time == 0 || this.state.time > 0) && this.state.isOn == 0 ? (
<Mutation mutation={TODO_OPERATIONS}>
{(todo_operations, { loading, error }) => (
<StartIcon
variant="contained"
color="primary"
className={[
classes.button,
hideTimerClass,
this.state.timerClass
]}
//className={this.state.timerClass}
onClick={event => {
this.startTimer();
todo_operations({
variables: {
id: this.props.developerlist.id,
status: "In Progress",
dtask_start_time: currdatetime,
time_tracking_flag: "yes",
developer_daily_hours: dailyhours,
is_task_started: "yes",
actual_start_date: currdatetime
}
});
}}
/>
)}
</Mutation>
) : null;
let stop =
this.state.isOn && this.state.isOn == 1 ? (
<Mutation mutation={STOP_TIMER}>
{(todo_operations, { loading, error }) => (
<StopIcon
variant="contained"
className={[classes.stopbutton, hideTimerClass]}
disabled={true}
onClick={event => {
this.stopTimer();
todo_operations({
variables: {
id: this.props.developerlist.id,
dtask_stop_time: currdatetime,
dtask_total_time: this.state.time,
time_tracking_flag: "stop"
}
});
}}
/>
)}
</Mutation>
) : null;
return (
<div>
<div className={classes.clock}>{ms(totalTaskTime)}</div>
{start}
{stop}
</div>
);
}
}
export default withStyles(styles, { withTheme: true })(TaskTimer);
if I'm understanding your problem correctly you are trying to cause an update in sister components when a certain event happens.
I believe that the best way to do this would be to have a state in your parent component (DeveloperTasks) that holds whether or not each timer should be disabled. Then pass a callback into each timer that would update the disabled list in the way that you're looking for.
The way I'm imagining such a callback would work is:
function getDisableOtherTimersCallback(timerNum) {
return timerNum => {
return this.state.setState({disabledList: disabledList.map((value, index) => index == timerNum)})
// this line just goes through and makes sure that the index corresponding to timerNum is the only one that's true
};
}
Then when you render your component for timer n you would pass in the timer you get for timer n.
<TaskTimer
developerlist={task}
hidetimer={this.state.disabledList[n]}
disableOtherTimersCallback={getDisableOtherTimersCallback(n)}
/>
Then whenever you want to disable the other timers you would call this method and your done!
(Also note that you can now use the disabledList to show or hide each timer!)
(Also also note that you need to add the disabledList to your state and initiate it in the way you are looking for)
Hope this helps!