1
\$\begingroup\$

I had to create a report which presents the salary structure of a firm.

The data needed was provided by the client as CSV-files.

I used PHP for the reading and preprocessing of the data. The actual presentation was done with HTML, CSS and JavaScript.

The markup and PHP which I have created:

<html>
 <head>
  <meta charset="UTF-8">
  <title>Analyse Vergütungsstruktur</title>
  <link href="./lib/bootstrap.min.css" rel="stylesheet" />
  <link href="./css/style.css" rel="stylesheet" />
 </head>
 <body>
 <div class="container">
    <h1>Analyse Vergütungsstruktur</h1>
    <div id="generalInfos">
      <ul class="flex-container">
        <li class="flex-item">Stand Abrechnungszeitraum</li>
        <li class="flex-item">MM.JJJJ</li>
      </ul>
      <ul class="flex-container">
        <li class="flex-item">Ausgewertete Firma/Standorte:</li>
        <li class="flex-item">Aufzählung der ausgewählten Firma/Standorte</li>
      </ul>
      <ul class="flex-container">
        <li class="flex-item">Anzahl ausgewertete Mitarbeiter</li>
        <li class="flex-item">Wert aus Opti VU</li>
      </ul>
    </div>
    <div id="accordion">

      <div class="card">
        <div class="card-header" id="headingOne">
          <h5 class="mb-0">
            <button class="btn btn-link" data-toggle="collapse" data-target="#collapseOne"
                    aria-expanded="true" aria-controls="collapseOne">
              Löhne und Gehälter
            </button>
          </h5>
        </div>

        <div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion">
          <div class="card-body">
            <table id="salaryTable" class="table table-stripped">
              <tr>
                <th>Gehälter</th>
                <th>Anzahl MA</th>
                <th>in %</th>
                <th>Stundenlöhne</th>
                <th>Anzahl MA</th>
                <th>in %</th>
              </tr>
            </table>
          </div>
        </div>
    </div>

    <div class="card">
        <div class="card-header" id="headingOne">
          <h5 class="mb-0">
            <button class="btn btn-link collapsed" data-toggle="collapse"
                    data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
              Monatliche brutto Zulagen und Zuschläge
            </button>
          </h5>
        </div>

    <div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordion">
      <div class="card-body">
        <table id="benefitsTable" class="table table-stripped">
          <tr>
            <th></th>
            <th>Anzahl MA</th>
            <th>Niedrigster Betrag</th>
            <th>Höchster Betrag</th>
            <th>Durchschnittlicher Betrag</th>
          </tr>
        </table>
      </div>
    </div>

    <div class="card">
        <div class="card-header" id="headingOne">
          <h5 class="mb-0">
            <button class="btn btn-link collapsed" data-toggle="collapse"
                    data-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
                Stundenlöhne
            </button>
          </h5>
        </div>

    <div id="collapseThree" class="collapse" aria-labelledby="headingThree" data-parent="#accordion">
      <div class="card-body">
        <table id="hourlySalariesTable" class="table table-stripped">
          <tr>
            <th>Stundenlohn Kategorien</th>
            <th>Anzahl MA</th>
          </tr>
        </table>
      </div>
    </div>

    </div>

    <fieldset class="radio-group">
      <legend>Auswahl Diagramm</legend>
      <div>
        <input type="radio" id="salaries" name="diagramSelection" value="salaries"
              checked>
        <label for="salaries">Gehalt</label>
      </div>

      <div>
        <input type="radio" id="benefits" name="diagramSelection" value="benefits">
        <label for="benefits">Zulagen</label>
      </div>

      <div>
        <input type="radio" id="hourlySalaries" name="diagramSelection" value="hourly">
        <label for="hourlySalaries">Stundenlöhne</label>
      </div>
    </fieldset>
    
    <div id="chart">
      <canvas id="doughnutChart" width="800" height="450"></canvas>
    </div>
  </div>

 <?php 
    function transferCsvToArray($fileName) {
      $fh = fopen("./data/$fileName.csv", "r");
 
      $retArray = array();
    
      while (($row = fgetcsv($fh, 0, ",")) !== FALSE) {
          $retArray[] = $row;
      }

      return $retArray;
   }

   $salaries = transferCsvToArray("gehaelter");
   $salaryBenefits = transferCsvToArray("zulagen");
   $hourlySalaries = transferCsvToArray("stundenlohn");
  ?>
  <script>
    const ooData = {};  // "oo" is a namespace for to avoid collisions.

    ooData.salaries = <?php echo json_encode($salaries) ?>;
    ooData.salaryBenefits = <?php echo json_encode($salaryBenefits) ?>;
    ooData.hourlySalaries = <?php echo json_encode($hourlySalaries) ?>;
  </script>
  <script src="./lib/jquery.js"></script>
  <script src="./lib/bootstrap.min.js"></script>
  <script src="./lib/chartjs.js"></script>
  <script src="./js/utils.js"></script>
  <script src="./js/main.js"></script>
 </body>
</html>

The JavaScript-files:

(() => {
    let chart = null;

    const createTableSalary = (salaries, totalEmployees) => {
        const hourlyEarnings = [
            "unter 8.84 €/Std.",
            "bis 11.50 €/Std.",
            "bis 14.37 €/Std.",
            "bis 17.24 €/Std.",
            "bis 20.1 €/Std.",
            "bis 22.99 €/Std.",
            "bis 25.43 €/Std.",
            "bis 28.74 €/Std.",
            "bis 31.61 €/Std.",
            "bis 34.48 €/Std.",
            "bis 37.36 €/Std.",
            "über 37.36 €/Std.",
        ];
       
        salaries.forEach((salary, index) => {
            const inPercent = ooUtils.computePercentage(totalEmployees, salary[1]);

            document.getElementById("salaryTable").insertAdjacentHTML(
            "beforeend", 
            `<tr class="salary-group">
                <td>${salary[0]}€</td>
                <td>${salary[1]}</td>
                <td>${inPercent}</td>
                <td>${hourlyEarnings[index]}</td>
                <td>${salary[1]}</td>
                <td>${inPercent}</td>
            </tr>`);
        });
    };
    
    const createBenefitsTable = (benefits) => { 
        benefits.forEach((benefit) => {
            const average 
                = (parseFloat(benefit[2], 10) + parseFloat(benefit[3], 10)) / 2;
            document.getElementById("benefitsTable").insertAdjacentHTML(
                "beforeend",
                `<tr class="benefit">
                    <td>${benefit[0]}</td>
                    <td>${benefit[1]}</td>
                    <td>${benefit[2]} €</td>
                    <td>${benefit[3]} €</td>
                    <td>${average.toFixed(2)} €</td>
                </tr>`
            );
        });
    };
    
    const createHourlySalariesTable = (salaries) => { 
        salaries.forEach((salary) => {
            document.getElementById("hourlySalariesTable").insertAdjacentHTML(
                "beforeend",
                `<tr class="benefit">
                    <td>${salary[0]}</td>
                    <td>${salary[1]}</td>
                </tr>`
            );
        });
    };
    
    const setUpChart = (table, chartTitle) => {
        const createChart = (labels, data, backgroundColor, titleText = "Darstellung als Diagramm") => {
            if (chart) {
                chart.destroy(); // Otherwise prior instances of chart want be removed somehow. And then they cause weird behaviour.
            }
    
            chart = new Chart(document.getElementById("doughnutChart"), {
                type: 'doughnut',
                data: {
                  labels,
                  datasets: [{
                    backgroundColor,
                    data
                  }]
                },
                options: {
                  title: {
                    display: true,
                    text: titleText
                  }
                }
            });
        };
    
        const mapFirstTwoColumns = (table) => {
            const columns = { first: [], second: [] };
    
            for (let i = 0; i < table.length; i++) {
                columns.first.push(table[i][0]);
                columns.second.push(table[i][1]);
            }
    
            return columns;
        };

        const columns = mapFirstTwoColumns(table);

        createChart(columns.first, columns.second, 
                        ooUtils.createRandomColors(columns.first.length),
                        chartTitle);
    };

    const switchChart = (idCurrent = "") => {
        switch (idCurrent) {   
            case "benefits":
                setUpChart(benefitsTruncated, "Monatliche brutto Zulagen und Zuschläge");
                break;
            case "hourlySalaries":
                setUpChart(hourlySalariesTruncated, "Stundenlöhne");
                break;
            default:
                setUpChart(salariesTruncated, "Löhne und Gehälter");
                break;
        }
    };
    
    const salariesTruncated = ooData.salaries.slice(1);
    createTableSalary(salariesTruncated, ooUtils.computeSum(salariesTruncated));
    
    const benefitsTruncated = ooData.salaryBenefits.slice(1);
    createBenefitsTable(benefitsTruncated);
    
    const hourlySalariesTruncated = ooData.hourlySalaries.slice(1);
    createHourlySalariesTable(hourlySalariesTruncated);
    
    document.querySelectorAll("[name=diagramSelection]").forEach((radioButton) => {
        radioButton.addEventListener("change", (event) => {
            switchChart(event.target.id);
        });
    });
    
    switchChart();
})();

const ooUtils = {};

ooUtils.computeSum = (salaries) => {
    return salaries.reduce((sum, current) => {
        return sum + parseInt(current[1], 10);
    }, 0);
};

ooUtils.computePercentage = (total, subSet)  => {
    return ((subSet * 100) / total).toFixed(1);
};

ooUtils.createRandomColors = (times) => {
    const createColor = () => Math.floor(Math.random() * 256);
    const randomColors = [];
    
    for (let i = 0; i < times; i++) {
        const randomColor =
            `rgb(${createColor()}, ${createColor()}, ${createColor()})`;

        randomColors.push(randomColor);
    }

    return randomColors;
};

The report looks decent and works fine.

Link to the GitHub-repository with the full project structure and screenshots: GitHub Repo

But not really satisfied with my code.

Is there a way to reduce the amount of repetition when creating the different tables?

Moreover:

Are my DOM-manipulations done in a good way and fashion?

In general: What would you have done differently and why?

Looking forward to reading for comments and answers.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.