DEV Community

Amalia Hajarani
Amalia Hajarani

Posted on

Lokomotif: Use Case (5: Implementing Vite-React)

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)