Create a timeline in a table shape

53 views Asked by At

I'm trying to create a pretty specific component that should look like this:

enter image description here

I give it an array with id, date and it should display the event in a timeline manner but in a table manner also since each event must be placed inside the month, with different spacing based on the date. On top of that there should be a radio button under the table align to its event (onClick the event should make the radio button active).

So far I succeeded in doing the timeline and the event but with equal and not in a table.

This is what it looks like:

enter image description here

And this is what the code looks like (using MUI and Minimal template):

import { useState } from 'react';
// mui
import { Box, Stack, Radio, FormControlLabel, FormControl, RadioGroup, Grid, Fab, Tooltip, Divider } from "@mui/material";
// components
import ScanCarousel from "./scan-carousel";

// ----------------------------------------------------------------------

const myObject = [
  {
    id: 1,
    date: "10/10/2021",
    label: "normal",
    scan: [
      '/assets/images/mock/front1.png',
      '/assets/images/mock/back1.png',
      '/assets/images/mock/lside1.png',
      '/assets/images/mock/rside1.png',
    ],
  },
  {
    id: 2,
    date: "11/10/2021",
    label: "critical",
    scan: [
      '/assets/images/mock/front2.png',
      '/assets/images/mock/back2.png',
      '/assets/images/mock/lside2.png',
      '/assets/images/mock/rside2.png',
    ],
  },
  {
    id: 3,
    label: "normal",
    date: "12/10/2021",
    scan: [
      '/assets/images/mock/front3.png',
      '/assets/images/mock/back3.png',
      '/assets/images/mock/lside3.png',
      '/assets/images/mock/rside3.png',
    ],
  },
];

// ----------------------------------------------------------------------

const ScanTimeline = () => {

  const [images, setImages] = useState(myObject[0].scan);
  const [value, setValue] = useState(myObject[2].date);
  const [openTooltips, setOpenTooltips] = useState({});

  const handleChange = (event) => {
    setValue(event.target.value);
  };

  const handleClick = (item) => {
    setOpenTooltips(prev => ({
      ...Object.keys(prev).reduce((acc, key) => {
        acc[key] = false;
        return acc;
      }, {}),
      [item.id]: !prev[item.id]
    })); setImages(item.scan);
    setValue(item.date);
  };

  return (
    <>
      <Stack direction='row'>
        <Box>
          <FormControl>
            <RadioGroup
              row
              value={value}
              onChange={handleChange}
            >
              {myObject.map((item) => (
                <Stack direction='column' key={item.id} alignItems='flex-start'>
                  <Stack direction='row' alignItems='center'>
                    <Fab
                      onClick={() => handleClick(item)}
                      aria-label="add"
                      size="small"
                      sx={{
                        color: 'grey',
                        backgroundColor: 'transparent',
                        border: `2px solid ${item.label === 'normal' ? '#0BE2CF' : "pink"}`,
                        boxShadow: 'none',
                        "&:hover": {
                          backgroundColor: "transparent"
                        }
                      }}>
                      {item.id}
                    </Fab>
                    <Divider sx={{ width: "40px", borderBottomWidth: 2 }} />
                  </Stack>
                  <FormControlLabel
                    labelPlacement="top"
                    value={item.date}
                    onClick={() => setImages(item.scan)}
                    sx={{ mr: 0, ml: "2px" }}
                    control={
                      <Tooltip
                        open={openTooltips[item.id] || false}
                        onOpen={() => setOpenTooltips(prev => ({ ...prev, [item.id]: true }))}
                        onClose={() => setOpenTooltips(prev => ({ ...prev, [item.id]: false }))}
                        title={item.date}
                      >
                        <Radio
                          sx={{
                            color: item.label === 'normal' ? '#0BE2CF' : "pink",
                            '&.Mui-checked': {
                              color: item.label === 'normal' ? '#0BE2CF ' : "pink",
                            },
                          }}
                        />
                      </Tooltip>}
                  />
                </Stack>
              ))
              }
            </RadioGroup>
          </FormControl>
        </Box>
      </Stack>
      <Grid container>
        <ScanCarousel previousScan={images} currentScan={myObject[2].scan} />
      </Grid>
    </>

  );
};

export default ScanTimeline;

The extent of it is that on clicking on and event you also change the content of the carousel/slider under the timeline.

I'm not looking for someone to code the entire thing but more for an idea of how to achieve this tricky component, some directions because so far after long reflexion I don't really know where to start.

Thank you.

1

There are 1 answers

0
Yohav Rn On

Ok so in the end the way to create this was to play with the positioning, here is how I did it based on my own code:

    <div style={{ width: `${monthsBetween.length * 150}px` }}>
        <Stack direction='row' sx={{ width: `${monthsBetween.length * 150}px`, p: 1.5, pl: 0, border: '1px solid lightgrey', borderRadius: '5px', position: 'relative' }}>
          {monthsBetween.map((month, index) => (
            <Box key={index} >
              <Typography sx={{ position: 'absolute', top: '-35px', left: `calc(150px * ${index + 1})`, transform: 'translateX(-50%)', textTransform: 'uppercase' }}>{month}</Typography>
              <Divider orientation='vertical' sx={{ position: 'absolute', top: '0', left: `calc(150px * ${index + 1})` }} />
            </Box>
          ))}
          <Divider sx={{ width: "100%", backgroundColor: 'lightgray', position: 'absolute', top: '50%', left: 0 }} />
          <Box>
            <FormControl>
              <RadioGroup
                row
                value={value}
                onChange={handleChange}
              >
                {myObject.map((item, index) => (
                  <Stack
                    direction='column'
                    key={item.id}
                    alignItems='flex-start'
                    sx={{
                      left: `calc(500px * ${index + 1})`,
                      position: 'absolute',
                    }}>
                    <Stack direction='row' alignItems='center'>
                      <Fab
                        onClick={() => handleClick(item)}
                        aria-label="add"
                        size="small"
                        sx={{
                          minHeight: '30px',
                          minWidth: '30px',
                          width: '30px',
                          height: '30px',
                          color: 'grey',
                          backgroundColor: 'white',
                          border: `2px solid ${item.label === 'normal' ? '#0BE2CF' : "pink"}`,
                          boxShadow: 'none',
                          "&:hover": {
                            backgroundColor: "white"
                          }
                        }}>
                        {item.id}
                      </Fab>
                    </Stack>
                    <FormControlLabel
                      labelPlacement="top"
                      value={item.date}
                      onClick={() => setImages(item.scan)}
                      sx={{ mr: 0, ml: "-3px", bottom: "-50px", position: "absolute" }}
                      control={
                        <Tooltip
                          open={openTooltips[item.id] || false}
                          onOpen={() => setOpenTooltips(prev => ({ ...prev, [item.id]: true }))}
                          onClose={() => setOpenTooltips(prev => ({ ...prev, [item.id]: false }))}
                          title={item.date}
                        >
                          <Radio
                            sx={{
                              color: item.label === 'normal' ? '#0BE2CF' : "pink",
                              '&.Mui-checked': {
                                color: item.label === 'normal' ? '#0BE2CF ' : "pink",
                              },
                            }}
                          />
                        </Tooltip>}
                    />
                  </Stack>
                ))
                }
              </RadioGroup>
            </FormControl>
          </Box>
        </Stack >
      </div>

I only need to figure out a algo to determine the exact position of each boutons on the timeline and find a way to make to overflow working (since so far it make the month text disappear..)

And here is the result:

enter image description here