import React, {useState, useEffect, ChangeEvent} from "react";
import {
  Badge, Box,
  Button,
  ButtonGroup,
  Chip,
  CircularProgress,
  Grid,
  Icon, IconButton, Skeleton, TextField,
  Typography
} from "@mui/material";
import ArrowUpwardRoundedIcon from '@mui/icons-material/ArrowUpwardRounded';
import ArrowDownwardRoundedIcon from '@mui/icons-material/ArrowDownwardRounded';
import {useAuthContext} from "../../AuthProvider/AuthProvider";
import FormatPrice from "../../Common/FormatPrice";
import {Order, readCandlesByTrade, readOrderByTrade, Trade, updateTrade} from "../../../actions/cicap-diary-trades";
import DataTable from "../../DataTable/DataTable";
import {useSnackbar} from "notistack";
import {CandleChart} from "../CandleChart/CandleChart";
import {TradeEntity} from "../CandleChart/CandleChart.props";
import {FormattedNumber} from "react-intl";
import ImageIcon from "next/image";
import {createTradeImage, deleteTradeImage, tradeImagesCollection, TradeImage} from "../../../actions/cicap-diary-trades-images";

const timezoneOffset = new Date().getTimezoneOffset() * 60;

interface SideTypeProps {
  order: Order;
}

const SideType = ({order}: SideTypeProps) => {
  let label = '';
  let color = 'default';
  const icon = order?.side && 'sell' === order.side
    ? ArrowDownwardRoundedIcon
    : ArrowUpwardRoundedIcon;

  switch (order?.type) {
    case 'limit':
      label = 'L';
      color = 'success'
      break;
    case 'market':
      label = 'M';
      color = 'warning'
      break;
    case 'stop_market':
      label = 'F';
      color = 'primary'
      break;
  }

  return <>
    <Badge
      badgeContent={label}
      // @ts-ignore
      color={color}
    >
      <Icon component={icon} color="action" />
    </Badge>
  </>
}

interface CandlePrice {
  timestamp: number;
  open: number;
  high: number;
  low: number;
  close: number;
  volume: number;
}

interface TradeDetailsProps {
  tradeId: string;
  defaultChartInterval: string;
}

const TradeDetails = ({tradeId, defaultChartInterval}: TradeDetailsProps) => {
  const [trade, setTrade] = useState<{orders: Array<Order>, data: Trade} | undefined>(undefined);
  const [orders, setOrders] = useState<Array<TradeEntity>>([]);
  const [chartInterval, setChartInterval] = useState(defaultChartInterval);
  const [waiting, setWaiting] = useState(false);
  const [candleData, setCandleData] = useState<Array<CandlePrice>>([]);
  const [images, setImages] = useState<Array<TradeImage>>([]);
  const [imageIdDeletion, setImageIdDeletion] = useState<string|null>(null);

  const {diaryToken} = useAuthContext();
  const [description, setDescription] = useState('');
  const [conclusion, setConclusion] = useState('');
  const [videoLink, setVideoLink] = useState('');
  const {enqueueSnackbar} = useSnackbar();
  const ref = React.createRef<HTMLInputElement>();

  const fetchTrade = () => {
    if (!tradeId || !diaryToken) {
      return;
    }

    setWaiting(true);
    readOrderByTrade(tradeId, diaryToken)
      .then(data => {
        setWaiting(false);
        if (!data) return;

        const newOrders: Array<TradeEntity> = [];

        data.orders.forEach(order => {
          const timestamp = Math.floor(order.msTimestamp) - timezoneOffset;

          newOrders.push({
            time: timestamp,
            position: order.side,
            value: parseFloat(order.price),
          });
        });

        setTrade(data);
        setOrders(newOrders);
      });
  }

  const fetchImages = () => {
    if (!tradeId || !diaryToken) {
      return;
    }

    tradeImagesCollection(tradeId, diaryToken)
      .then(data => {
        if (!data) return;
        setImages(data.data)
      })
  }
  
  const createImage = (name: string, content: string) => {
    if (!tradeId || !diaryToken) {
      return;
    }

    createTradeImage(tradeId, {name:  name, content: content}, diaryToken)
     .then(data => {
        if (!data) return;
        setImages([...images, data.data])
     })
  }
  const fetchCandleData = () => {
    if (!tradeId || !diaryToken) {
      return;
    }

    setWaiting(true);
    readCandlesByTrade(tradeId, chartInterval, diaryToken)
      .then(data => {
        setWaiting(false);
        if (!data) return;

        // @ts-ignore
        const candles = data.data;
        const dataLength = candles.length;
        const kLines = [];

        for (let i = 0; i < dataLength; i += 1) {
          const timestamp = Math.floor(candles[i][0]) - timezoneOffset;

          kLines.push({
            timestamp: timestamp,
            open: parseFloat(candles[i][1]),
            high: parseFloat(candles[i][2]),
            low: parseFloat(candles[i][3]),
            close: parseFloat(candles[i][4]),
            volume: parseFloat(candles[i][5]),
          });

        }
        setCandleData(kLines);
      })
  }

  const updateTradeHandle = () => {
    if (!diaryToken) return;
    if (!trade?.data.id) return;

    updateTrade(trade?.data.id, description, conclusion, videoLink, diaryToken)
      .then(data => {
        if (!data) return;

        fetchTrade();
        enqueueSnackbar('Сделка обновлена.', {variant: 'success'});
      })
  }

  const onMainPhotoSelected = (e: ChangeEvent<HTMLInputElement>) => {
    if (!e.target.files) return

    const file = e.target.files[0]
    const reader = new FileReader()

    reader.onloadend = () => {
      if (!reader.result) return
      createImage(`chart-${tradeId}-${images.length}`, reader.result.toString())
    }
    reader.readAsDataURL(file)
  }

  const removeGalleryImage = (imageId: string) => {
    if (!tradeId || !diaryToken) {
      return;
    }

    setImageIdDeletion(imageId)
    deleteTradeImage(tradeId, imageId, diaryToken)
    .then(data => {
      if (!data) return;
      
      fetchImages();
      enqueueSnackbar('Изображение удалено', {variant: 'success'});
      setImageIdDeletion(null)
    })
  }

  useEffect(() => {
    fetchTrade();
    fetchCandleData();
    fetchImages()
  }, [tradeId]);

  useEffect(() => {
    fetchCandleData();
  }, [chartInterval]);

  useEffect(() => {
    setDescription(trade?.data?.description || '');
    setConclusion(trade?.data?.conclusion || '');
    setVideoLink(trade?.data?.videoLink || '');
  }, [trade]);

  return <>
    <Box sx={{p: 2, pb: 4, backgroundColor: '#f5f5f5'}}>
      <Box sx={{mb: 2}}>
        <Typography variant="h6" component="span">
          {trade?.data?.symbolName} {trade?.data?.side.toUpperCase()}
        </Typography>
        &nbsp;&nbsp;
        <Chip
          label={`Процент: ${parseFloat(`${trade?.data?.percentDiffPrice}`).toFixed(Math.abs(parseFloat(`${trade?.data?.percentDiffPrice}`)) < 0.1 ? 3 : 2)}%`}
          color={parseFloat(`${trade?.data?.percentDiffPrice}`) >= 0 ? 'success' : 'error'}
        />
        &nbsp;
        <Chip
          label={<>Прибыль: ${FormatPrice({ amount: parseFloat(`${trade?.data?.profit}`) || 0, currency_iso_code: 'USD'})}</>}
        />
      </Box>

      <Grid container spacing={2}>
        <Grid item xs={12} md={6} sx={{pr: 2}}>

          <ButtonGroup variant="outlined" style={{ marginBottom: '1rem' }}>
            {
              ["1m", "3m", "5m", "15m", "30m", "1h", "4h", "12h", "1d"].map(interval => (
                <Button
                  key={interval}
                  sx={{px: 1, py: .5, fontSize: '.75rem'}}
                  variant={interval === chartInterval ? "contained" : "outlined"}
                  onClick={() => setChartInterval(interval)}
                >{interval}</Button>
              ))
            }
          </ButtonGroup>
          &nbsp;
          {
            waiting ? (
              <CircularProgress
                style={{position: "absolute", marginLeft: '1rem'}}
                color="inherit"
                size={32}
              />
            ) : (<></>)
          }
          <Box sx={{pr: 2}}>
            {
              trade?.data.id && candleData ? (<>
                <CandleChart
                  images={images}
                  candles={candleData}
                  tradeId={trade?.data.id}
                  orders={orders}
                  interval={chartInterval}
                  openPrice={trade?.data.openPrice}
                  closePrice={trade?.data.closePrice}
                  pricePrecision={trade.data.pricePrecision}
                  quantityPrecision={trade.data.quantityPrecision}
                  createImage={createImage}
                />
              </>) : (<>
                <Skeleton variant="rectangular" height={500} />
              </>)
            }
          </Box>
        </Grid>
        <Grid item xs={12} md={3}>
          {trade?.orders && Array.isArray(trade.orders) ? (
            <DataTable
              sx={{fontSize: '.75rem', '& .MuiDataGrid-cell': {py: 2}}}
              rows={trade.orders}
              hideFooter
              columns={[
                {flex: 1, field: 'side', headerName: 'Сторона', sortable: false, filterable: false, renderCell: cell => (<>
                    {cell.value.toUpperCase()} <SideType order={cell.row} />
                  </>)},
                {flex: 1, field: 'quantity', headerName: 'Оборот', sortable: false, filterable: false },
                {flex: 1, field: 'price', headerName: 'Цена', sortable: false, filterable: false, renderCell: cell => (<>
                    <FormattedNumber
                      value={cell.value}
                      minimumFractionDigits={0}
                      maximumFractionDigits={parseInt(trade?.data.pricePrecision || '10')}
                    /> $
                  </>)},
                {flex: 1, field: 'amount', headerName: 'Объем', sortable: false, filterable: false, renderCell: cell => (<>
                    <FormattedNumber
                      value={cell.value}
                      minimumFractionDigits={0}
                      maximumFractionDigits={parseInt(trade?.data.quantityPrecision || '10')}
                    /> $
                  </>)},
                {flex: 1, field: 'profit', headerName: 'Доход', sortable: false, filterable: false, renderCell: cell => (<>
                    <FormattedNumber
                      value={cell.value}
                      minimumFractionDigits={0}
                      maximumFractionDigits={4}
                    /> $
                  </>)},
              ]}
            />
          ) : (<></>)}

          <Box sx={{my: 2}}>
            <Chip style={{ width: '1.5rem' }} size="small" label="M" color="warning" /> - Market order,&nbsp;&nbsp;
            <Chip style={{ width: '1.5rem' }} size="small" label="L" color="success" /> - Limit order,&nbsp;&nbsp;
            <Chip style={{ width: '1.5rem' }} size="small" label="F" color="primary" /> - Funding&nbsp;
          </Box>

          <Box sx={{ my: 2 }}>
            <Grid display="flex">
              <Typography>Галерея</Typography>
              <input
                ref={ref}
                type='file'
                accept=".jpg, .jpeg"
                style={{ display: 'none' }}
                onChange={onMainPhotoSelected}
              />
              <Button sx={{ ml: 3, width: 60, height: 25 }} onClick={() => ref && ref.current?.click()}>
                добавить
              </Button>
            </Grid>
            <Grid mt={3} >
              {
                images.map((image, index) => <span style={{ position: "relative", margin: "5px" }} key={index}>
                  <a target='_blank' rel='noreferrer' href={image.url}>
                    <ImageIcon
                      style={{ cursor: "pointer", opacity: imageIdDeletion === image.id ? 0.4 : 1, margin: 5 }}
                      key={image.id + index}
                      src={image.url}
                      width={160}
                      height={120}
                      alt=""
                    />
                  </a>
                  {
                    imageIdDeletion === image.id ?
                      <CircularProgress
                        style={{ position: "absolute", left: 55, top: -65 }}
                        color="error"
                        size={42}
                      /> : <></>
                  }
                  <IconButton color="error" sx={{ position: "absolute", right: 0, top: -114, borderRadius: 2 }}
                    onClick={() => removeGalleryImage(image.id)}
                  >
                    x
                  </IconButton>
                </span>
                )
              }
            </Grid>
          </Box>

        </Grid>
        <Grid item xs={12} md={3}>

          <Typography>Описание:</Typography>
          <TextField
            fullWidth
            sx={{mb: 2}}
            variant="outlined"
            value={description} onChange={e => {
              setDescription(e.target.value);
            }}
            multiline
            rows={3}
          />
          <Typography>Вывод:</Typography>
          <TextField
            fullWidth
            sx={{mb: 2}}
            variant="outlined"
            value={conclusion} onChange={e => {
              setConclusion(e.target.value);
            }}
            multiline
            rows={3}
          />
          <Typography>Ссылка на видео:</Typography>
          <TextField fullWidth variant="outlined" value={videoLink} onChange={e => {
            setVideoLink(e.target.value);
          }} />
          <Button color="primary" variant="contained" sx={{mt: 2}} onClick={updateTradeHandle}>Сохранить</Button>
        </Grid>
      </Grid>

    </Box>
  </>
}

export default TradeDetails;

import React, {useEffect, useRef, useState} from "react";
import {
  init,
  dispose,
  Chart,
  DeepPartial,
  IndicatorFigureStylesCallbackData,
  Indicator,
  IndicatorStyle,
  KLineData,
  utils,
} from "klinecharts";
import {CandleChartProps} from "./CandleChart.props";
import CandleChartToolbar from "./CandleChartToolbar";
import {Style} from "util";
import {Box, Icon, IconButton, Stack} from "@mui/material";
import getMinutesTickSizeByInterval from "./utils/getMinutesTickSizeByInterval.util";
import drawTrade from "./utils/drawTrade.util";
import drawTradeLines from "./utils/drawTradeLines.util";
import {BasketIcon, ScreenIcon} from "../../icons";
import {FullScreen, useFullScreenHandle} from "react-full-screen";;

// @ts-ignore
const chartStyles: DeepPartial<Style> = {
  candle: {
    bar: {
      upColor: "#4caf50",
      downColor: "#d32f2f",
      noChangeColor: ""
    },
    priceMark: {
      high: {
        textFamily: "Mont",
      },
      low: {
        textFamily: "Mont",
      },
      last: {
        text: {
          family: "Mont",
        }
      }
    },
    tooltip: {
      text: {
        family: "Mont",
        marginLeft: 10,
        marginTop: 8,
        marginRight: 10
      }
    }
  },
  indicator: {
    ohlc: {
      upColor: "red",
      downColor: "red",
      noChangeColor: "red"
    },
    bars: [{
      upColor: "#4caf50",
      downColor: "#d32f2f",
      noChangeColor: ""
    }],
    lastValueMark: {
      show: false,
      text: {
        family: "Mont",
      }
    },
    tooltip: {
      text: {
        family: "Mont",
      },
    },
  },
  xAxis: {
    tickText: {
      family: "Mont",
    }
  },
  yAxis: {
    type: "log",
    tickText: {
      family: "Mont",
    },
  },
  crosshair: {
    horizontal: {
      text: {
        family: "Mont",
      }
    },
    vertical: {
      text: {
        family: "Mont",
      }
    }
  },
  overlay: {
    text: {
      family: "Mont",
    },
    rectText: {
      family: "Mont",
      backgroundColor: "#686D76",
    }
  }
}

interface Vol {
  volume?: number
}

export const CandleChart = ({
  images,
  candles,
  tradeId,
  orders,
  interval,
  openPrice,
  closePrice,
  pricePrecision,
  quantityPrecision,
  createImage
}: CandleChartProps) => {
  console.log(candles);
  
  const chart = useRef<Chart|null>();
  const paneId = useRef<string>("");
  const [figureId, setFigureId] = useState<string>("")
  const ref = useRef<HTMLDivElement>(null);
  const handle = useFullScreenHandle();


  
  const onWindowResize = () => chart.current?.resize()

  useEffect(() => {
    window.addEventListener("resize", onWindowResize);
    onWindowResize();
    return () => {
      window.removeEventListener("resize", onWindowResize);
    };
  }, [ref, handle]);
  
  useEffect(() => {
    chart.current = init(`chart-${tradeId}`, {styles: chartStyles});

    return () => dispose(`chart-${tradeId}`);
  }, [tradeId]);

  useEffect(() => {
    const onWindowResize = () => chart.current?.resize();
    window.addEventListener("resize", onWindowResize);

    return () => window.removeEventListener("resize", onWindowResize);
  }, []);

  useEffect(() => {
    chart.current?.applyNewData(candles);
    chart.current?.overrideIndicator({
      name: "VOL",
      shortName: "Объем",
      calcParams: [],
      figures: [
        {
          key: "volume",
          title: "",
          type: "bar",
          baseValue: 0,
          styles: (data: IndicatorFigureStylesCallbackData<Vol>, indicator: Indicator, defaultStyles: IndicatorStyle) => {
            const kLineData = data.current.kLineData as KLineData
            let color: string
            if (kLineData.close > kLineData.open) {
              color = utils.formatValue(indicator.styles, "bars[0].upColor", (defaultStyles.bars)[0].upColor) as string
            } else if (kLineData.close < kLineData.open) {
              color = utils.formatValue(indicator.styles, "bars[0].downColor", (defaultStyles.bars)[0].downColor) as string
            } else {
              color = utils.formatValue(indicator.styles, "bars[0].noChangeColor", (defaultStyles.bars)[0].noChangeColor) as string
            }
            return { color }
          }
        }
      ]
    }, paneId.current);
    chart.current?.createIndicator("VOL", false, { id: paneId.current });
    chart.current?.setPriceVolumePrecision(+pricePrecision, +quantityPrecision);
  }, [candles]);

  useEffect(() => {
    if (!orders || orders.length === 0 || candles.length === 0) return;

    const minTime = orders[0].time;
    const maxTime = orders[orders.length - 1].time;
    const needleTime = minTime + (maxTime - minTime) / 2;

    chart.current?.scrollToTimestamp(needleTime + 45 * getMinutesTickSizeByInterval(interval) * 60 * 1000);
    drawTrade(chart, paneId, orders, interval);
    if (openPrice && closePrice) {
      let openTime = Infinity;
      let closeTime = -Infinity;

      orders.forEach(order => {
        if (openTime > order.time) {
          openTime = order.time;
        }

        if (closeTime < order.time) {
          closeTime = order.time;
        }
      });

      drawTradeLines(
        chart,
        openPrice,
        openTime,
        closePrice,
        closeTime,
        orders[0].position,
        paneId,
        pricePrecision,
        quantityPrecision,
      );
    }
  }, [orders, candles, tradeId]);

  const removeFigure = () => {
    chart.current?.removeOverlay({
      id: figureId
    })
    setFigureId("")
  } 

  const onButtonClick = async () => {
    const imgUrl = chart.current?.getConvertPictureUrl(true)
    if (!imgUrl) return
    createImage(`chart-${tradeId}-${images.length}`, imgUrl)
      const link = document.createElement("a");
      link.setAttribute("href", imgUrl);
      link.setAttribute("download", `chart-${tradeId}.jpg`);
  
      link.click();
  }

  return (<>
    <FullScreen handle={handle}>
      <Box height={!handle.active ? 590 : "100%"} sx={{ position: "relative", background: "#ffffff" }}>
         
        <Box sx={{ borderBottom: "1px solid #ddd" }}>
          <Box sx={{ borderLeft: "1px solid #ddd", ml: "55px" }}>
            <IconButton sx={{ borderRadius: 2, ml: 1, fontSize: "1rem", fontFamily: "Mont", color: "#677294" }} onClick={onButtonClick}>
              <Icon component={ScreenIcon} /> Screenshot
            </IconButton>
          </Box>
        </Box>

        <Stack direction="row" height={!handle.active ? 550 : "100%"} width="100%">
          <CandleChartToolbar
            setFigureId={setFigureId}
            chart={chart} paneId={paneId}
            handle={handle}
          />
           <Box
            ref={ref}
            id={`chart-${tradeId}`}
            width="calc(100% - 55px)"
            height={!handle.active ? 550 : "100%"}
            sx={{ borderLeft: "1px solid #ddd" }}
          >
            {
              figureId.length > 0 &&
              <Stack
                sx={{
                  backgroundColor: "#CBD4E3",
                  borderRadius: 1,
                  position: "absolute",
                  zIndex: 10,
                  right: 80,
                  top: 30,
                  border: "1px solid #697669",
                }}
                spacing={2}
              >
                <IconButton sx={{ borderRadius: 1 }} onClick={removeFigure}>
                  <Icon component={BasketIcon} />
                </IconButton>
              </Stack>
            }
          </Box>
        </Stack>

      </Box>
      </FullScreen>
  </>);
}
В тех сделках, где нет закрытого ордера нужно отображать на графике свечи в реальном времени и по текущей цене рассчитывать процент. Это нужно, чтобы можно было следить за сделкой в реальном времени.
Нужно чтобы график отображал текущую ситуацию на рынке, как здесь https://www.binance.com/ru/futures/DOGEUSDT