Lokomotif: Use Case (5: Implementing Vite-React)
Tags: javascript, beginners, tutorial, react
Published: 2023-11-22 01:53:23

Prerequisites:

1. Node version of 16.20.2

And now, let's go into the cooking step:

## Initialize Node application
1. Create a directory dedicated for this project. For me the name is front-end-loko-kai
2. Open command prompt from that directory
3. Run this command:
    ```
    npm create vite .
    ```
4. Choose the framework by using up and down arrow keys, I choose React. Then hit enter
5. Choose the programming language, I use plain JavaScript, then hit enter
6. Et voila, your application boilerplate is done.

## Install npm packages
1. Installing `mui`. This package will be use as css framework
    ```
    npm install @mui/material @emotion/react @emotion/styled
    ```
2. Installing `axios`
    ```
    npm install axios
    ```
3. Installing chart dependencies
    ```
    npm install chart.js react-chartjs-2
    ```

## Creating .env
This is how my `.env` lookslike:
```
VITE_BASE_URL=http://localhost:8080
```

## Creating project directory structure
As you get boilerplate, you might want to delete some of it because of its irrelevancy. Below is project structure that I use:
```
*src
    - assets
    - components
    - services
    - utils
    - views
    - App.jsx
    - index.css
    - main.jsx
    - theme.js
*.env
*package.json
```

## Creating global theme
I want to use Roboto font instead the built in one and make sure that my typography is responsive. So in `theme.js` I put these lines:
```
import { createTheme, responsiveFontSizes } from '@mui/material/styles';

let theme = createTheme({
    typography: {
        fontFamily: [
            'Roboto', 
            'sans-serif'
        ].join(',')
    }
});

theme = responsiveFontSizes(theme);

export default theme;
```

To apply the theme in your application, you can update your `App.jsx` like this:
```
import { ThemeProvider } from '@mui/material'
import theme from './theme'
import Dashboard from './views/Dashboard'

function App() {

  return (
    <ThemeProvider theme={theme}>
      <Dashboard/>
    </ThemeProvider>
  )
}

export default App
```
Don't worry about `Dashboard` component, we will be there soon. Then, if you want more independency of styling the application you might wanna update `index.css`. Here's mine:
```
:root {
  font-family: 'Roboto', sans-serif;
  line-height: 1.5;
  background: linear-gradient(90deg, rgba(253,202,198,1) 0%, rgba(252,248,223,1) 100%);

  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
```

## Creating views
I only make a very simple dashboard. Hence, I only have one view which is dashboard.
1. In `view` directory, create a new directory called `Dashboard`
2. Inside `Dasboard` directory, create a file called `index.jsx`. This is the content of the file:
    ```
    import { Container, Grid, Typography } from '@mui/material';
    import React from 'react'
    import Header from '../../components/Header';
    import ReportSummary from '../../components/ReportSummary';
    import StatusDescription from '../../components/StatusDescription';
    
    const centerContentStyles = {
      width: '100%',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
    };
    
    function Dashboard() {
      return (
        <div>
          <Container >
            <Grid spacing={3} container direction={"column"} alignItems={"center"} justifyContent={"center"} sx={{ height: '97vh' }}>
              <Grid item md={2}  sx={ centerContentStyles }>
                <Header/>
              </Grid>
              <Grid item md={4} sx={ centerContentStyles }>
                <Grid spacing={4} container sx={{height: '100%'}}>
                  <Grid item md={5}>
                    <StatusDescription />
                  </Grid>
                  <Grid item md={7}>
                    <ReportSummary />
                  </Grid>
                </Grid>
              </Grid>
            </Grid>
          </Container>
        </div>
      )
    }
    
    export default Dashboard;
    ```

## Creating Header component
1. In `components` directory, create a new directory called `Header`
2. Inside `Header` directory, create a file called `index.jsx`. This is the content of the file:
    ```
    import { Container, Grid, Typography } from '@mui/material';
    import React from 'react'
    
    function Header() {
      return (
        <div style={{ 
          display:'flex', 
          alignItems: 'center',  
          width: '100%', 
          height: '70%', 
          backgroundColor: '#FDF2EF',
          borderRadius: '1rem' 
        }}>
          <Container sx={{ textAlign: 'center' }}>
            <Typography variant='h4' fontWeight={"bold"} sx={{ color: '#ad917f' }}>Lokomotif Dashboard</Typography>
          </Container>
        </div>
      )
    }
    
    export default Header;
    ```

## Creating Status Description component
1. In `components` directory, create a new directory called `StatusDescription`
2. Inside `StatusDescription` directory, create a file called `index.jsx`. This is the content of the file:
    ```
    import { Grid, Typography } from '@mui/material';
    import React from 'react';
    import { statusDescription } from '../../utils/Constants';
    
    function StatusDescription() {
      const detailedStatusDescription = statusDescription;
    
      return (
        <Grid 
          container 
          gap={2}
          sx={{
            display:'flex', 
            flexDirection: 'column',
            alignItems: 'left',  
            width: '100%', 
            height: '100%', 
            backgroundColor: '#FDF2EF',
            borderRadius: '1rem',
            padding: '1rem' 
          }}
        > 
          <Grid item>
            <Typography variant='h6' fontWeight={"bold"} sx={{ color: '#855ef5' }}>Lokomotif Status Description</Typography>
          </Grid>
          <Grid item>
            {
              detailedStatusDescription.map((status) => {
                return (
                  <Typography sx={{ marginBottom: '0.25rem' }}>
                    Status of <span style={{ color: '#855ef5', fontWeight:'bold' }}>{status.status}</span>: {status.description}
                  </Typography>
                )
              })
            }
          </Grid>
        </Grid>
      )
    }
    
    export default StatusDescription;
    ```
3. Create a JSON containing status locomotive and its description by adding new file called `Constants.js` inside `utils` folder
    ```
    export const statusDescription = [
        {
            status: 1,
            description: "New Lokomotif"
        },
        {
            status: 2,
            description: "In Use Lokomotif"
        },
        {
            status: 3,
            description: "Scheduled Maintenance"
        },
        {
            status: 4,
            description: "Under Repair"
        },
        {
            status: 5,
            description: "Idle Lokomotif"
        },
        {
            status: 6,
            description: "Out of Service - Major Repairs"
        },
        {
            status: 7,
            description: "Testing Phase"
        },
        {
            status: 8,
            description: "Retired Lokomotif"
        },
        {
            status: 9,
            description: "Pending Inspection"
        },
        {
            status: 10,
            description: "Emergency Standby"
        }
    ];
    ```

## Creating Report Summary component
This component will be causing error since we are not yet defining API calls service and a helper function. But we will got into it soon.
1. In `components` directory, create a new directory called `ReportSummary`
2. Inside `ReportSummary` directory, create a file called `index.jsx`. This is the content of the file:
    ```
    import { Grid, Typography } from '@mui/material'
    import React, { useEffect, useState } from 'react'
    import { Chart } from 'chart.js/auto'
    import { CategoryScale } from 'chart.js/auto'
    import { Bar } from 'react-chartjs-2'
    import Grid2 from '@mui/material/Unstable_Grid2/Grid2'
    import { getAllLokomotifFromYesterday } from '../../services/lokomotif'
    import { groupLokomotifStatusandTotal } from '../../utils/HelperFunctions'
    
    Chart.register(CategoryScale);
    
    function ReportSummary() {
    
      const [chartData, setChartData] = useState({});
      const [isFetched, setIsFetched] = useState(false);
    
      useEffect(() => {
        getAllLokomotifFromYesterday()
          .then((res) => {
            const data = groupLokomotifStatusandTotal(res);
            const labels = data.map((lokomotif => lokomotif.status + 1));
            const datasets = {
              label: 'Total lokomotif in database',
              data: data.map((lokomotif => lokomotif.total)),
              backgroundColor: ["#ad917f"]
            }
            
            setChartData({
              labels: labels,
              datasets: [datasets],
            })
    
            setIsFetched(true);
    
          }).catch((err) => {
            console.log(err);
          });
      }, []);
    
      return (
        <Grid 
          container 
          gap={2}
          sx={{
            display:'flex', 
            flexDirection: 'column',
            alignItems: 'left',  
            width: '100%', 
            height: '100%', 
            backgroundColor: '#FDF2EF',
            borderRadius: '1rem',
            padding: '1rem' 
          }}
        > 
          <Grid item>
            <Typography variant='h6' fontWeight={"bold"} sx={{ color: '#855ef5' }}>Lokomotifs Status Statistic in Database</Typography>
          </Grid>
          <Grid2 item>
            {
              isFetched && (
                <Bar 
                  data={chartData} 
                  options={{
                    plugins: {
                      legend: {
                        display: false
                      }
                    },
                    scales: {
                      x: {
                        title: {
                          text: 'Lokomotif status',
                          display: true
                        }
                      },
                      y: {
                        title: {
                          text: 'Total lokomotif in database',
                          display: true
                        },
                        ticks: {
                          stepSize: 1
                        }
                      },
                    }
                  }}
                />
              )
            }
          </Grid2>
        </Grid>
      )
    }
    
    export default ReportSummary
    ```

## Creating a helper function
Since the summary will only contain locomotive status and its total number in database, we need some function to create an object as what we need since API return will be a whole different object in term of structure.
1. Inside `utils` directory, create a file called `HelperFunction.js`
    ```
    export const groupLokomotifStatusandTotal = (lokomotifs) => {
        const totalStatus = 10;
        let grouppedLokomotif = [];
    
        for(var i = 0; i < totalStatus; i++) {
            let totalLokomotif = lokomotifs.filter(lokomotif => lokomotif.status === i.toString()).length;
            let temp = {
                status: i,
                total: totalLokomotif
            }
    
            grouppedLokomotif.push(temp);
        }
    
        return grouppedLokomotif;
    }
    ```

## Creating API call service
1. Inside `services` directory, create a directory called `config`
2. Inside `config` directory, create a file called `axios.js`
    ```
    import axios from "axios";
    
    const BASE_URL = import.meta.env.VITE_BASE_URL;
    
    const api = axios.create({
        baseURL: BASE_URL,
    });
    
    export default api;
    ```
3. Inside `services` directory, create a directory called `lokomotif`
4. Inside `lokomotif` directory, create a file called `index.js`
    ```
    import api from "../config/axios";
    
    export const getAllLokomotifFromYesterday = async () => {
        try {
            const res = await api.get('/lokomotifs');
            const pageSize = res.data.page.totalPages;
    
            let allLokomotifData = [];
            for(var i = 0; i < pageSize; i++) {
                const resOnPage = await getLokomotifDataByPage(i)
                allLokomotifData = [...allLokomotifData, ...resOnPage];;
            }
    
            return allLokomotifData;
        } catch (error) {
            console.log(error);
        }
    };
    
    const getLokomotifDataByPage = async (page) => {
        try {
            const res = await api.get(`/lokomotifs?page=${page}`);
            return res.data._embedded.lokomotifs;
        } catch (error) {
            console.log(error);
        }
    }
    ```

## Running the application
1. Make sure Spring Boot: Scheduler Report service is run.
2. Open command prompt from the project directory and run this command:
    ```
    npm run dev
    ```
3. If the project runs correctly, below is the UI you will see
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ijdzpxghl2wj340k7f9.png)