From c470241f09b9ed29a0c156b401d09960ec728180 Mon Sep 17 00:00:00 2001 From: Denis Thiessen Date: Wed, 31 Jan 2024 00:24:57 +0100 Subject: [PATCH] Custom CSV parser for fixing parsing issues and manual --- src/components/SingleFileUpload.jsx | 130 ++++++++++++++++----------- src/components/TimelineAreaChart.jsx | 4 +- src/index.js | 15 +++- 3 files changed, 93 insertions(+), 56 deletions(-) diff --git a/src/components/SingleFileUpload.jsx b/src/components/SingleFileUpload.jsx index f82517f..24c80ed 100644 --- a/src/components/SingleFileUpload.jsx +++ b/src/components/SingleFileUpload.jsx @@ -1,52 +1,43 @@ import React, { useState, useEffect } from "react" -import csv from "csvtojson" import PropTypes from "prop-types" -export var fileContent = "{}"; -var uploadFailed = false; +export var fileContent = "{}" +var uploadFailed = false function SingleFileUpload({ onFileUploaded }) { const [, setFile] = useState() - const [fileUploaded, setFileUploaded] = useState(false); + const [fileUploaded, setFileUploaded] = useState(false) useEffect(() => { if (fileUploaded) { onFileUploaded() } - if(uploadFailed && !fileUploaded) { - onFileUploaded(); + if (uploadFailed && !fileUploaded) { + onFileUploaded() } }, [fileUploaded]) // Reads file. - var readCallback = function (content) { - csv() - .fromString(content) - .preRawData((csvRawData) => { - - if(!checkCSVValidity(csvRawData)) { - alert("Invalid CSV File"); - uploadFailed = true; - setFileUploaded(false); - fileContent = {}; - return "{}"; - } - - csvRawData = csvRawData.replaceAll("Start Date", "Start_Date"); - csvRawData = csvRawData.replaceAll("Start Time", "Start_Time"); - csvRawData = csvRawData.replaceAll("End Date", "End_Date"); - csvRawData = csvRawData.replaceAll("End Time", "End_Time"); - csvRawData = csvRawData.replaceAll("Duration (decimal)", "Duration_Decimal"); - uploadFailed = false; - - return csvRawData - }) - .then((csvRow) => { - if(!uploadFailed) { - fileContent = csvRow; - setFileUploaded(true); - } - }) + var readCallback = function (csvRawData) { + if (!checkCSVValidity(csvRawData)) { + alert("Invalid CSV File") + uploadFailed = true + setFileUploaded(false) + fileContent = {} + return "{}" + } + + csvRawData = csvRawData.replaceAll("Start Date", "Start_Date") + csvRawData = csvRawData.replaceAll("Start Time", "Start_Time") + csvRawData = csvRawData.replaceAll("End Date", "End_Date") + csvRawData = csvRawData.replaceAll("End Time", "End_Time") + csvRawData = csvRawData.replaceAll("Duration (decimal)", "Duration_Decimal") + uploadFailed = false + + if (!uploadFailed) { + fileContent = csvJSON(csvRawData) + setFileUploaded(true) + } } function handleChange(event) { @@ -56,11 +47,11 @@ function SingleFileUpload({ onFileUploaded }) { const reader = new FileReader() reader.readAsText(changedFile) reader.onload = function (evt) { - var uploadedFile = document.getElementById('fileInput').files[0]; + var uploadedFile = document.getElementById("fileInput").files[0] if (uploadedFile.type != "application/vnd.ms-excel") { - alert("Wrong file type"); - fileContent = "{}"; - return; + alert("Wrong file type") + fileContent = "{}" + return } readCallback(evt.target.result) } @@ -76,19 +67,58 @@ function SingleFileUpload({ onFileUploaded }) { ) } +function csvJSON(csvText) { + let lines = [] + const linesArray = csvText.split("\n") + // for trimming and deleting extra space + linesArray.forEach((e) => { + const row = e.replace(/[\s]+[,]+|[,]+[\s]+/g, ",").trim() + lines.push(row) + }) + // for removing empty record + lines.splice(lines.length - 1, 1) + const result = [] + const headers = lines[0].split(",") + + for (let i = 1; i < lines.length; i++) { + const obj = {} + const currentline = lines[i].split(",") + + for (let j = 0; j < headers.length; j++) { + obj[headers[j]] = currentline[j] + } + result.push(obj) + } + + var resultString = JSON.stringify(result) + resultString = resultString.replaceAll('\\"', "") + return JSON.parse(resultString) +} + function checkCSVValidity(rawCSV) { - const containsProjectKey = rawCSV.includes("\"Project\""); - const containsDescriptionKey = rawCSV.includes("\"Description\""); - const containsTaskKey = rawCSV.includes("\"Task\""); - const containsUserKey = rawCSV.includes("\"User\""); - const containsStartDateKey = rawCSV.includes("\"Start Date\""); - const containsStartTimeKey = rawCSV.includes("\"Start Time\""); - const containsEndDateKey = rawCSV.includes("\"End Date\""); - const containsEndTimeKey = rawCSV.includes("\"End Time\""); - const containsDurationHourKey = rawCSV.includes("\"Duration (h)\""); - const containsDurationDecimalKey = rawCSV.includes("\"Duration (decimal)\""); - - return containsProjectKey && containsDescriptionKey && containsTaskKey && containsUserKey && containsStartDateKey && containsStartTimeKey && containsEndDateKey && containsEndTimeKey && containsDurationHourKey && containsDurationDecimalKey; + const containsProjectKey = rawCSV.includes('"Project"') + const containsDescriptionKey = rawCSV.includes('"Description"') + const containsTaskKey = rawCSV.includes('"Task"') + const containsUserKey = rawCSV.includes('"User"') + const containsStartDateKey = rawCSV.includes('"Start Date"') + const containsStartTimeKey = rawCSV.includes('"Start Time"') + const containsEndDateKey = rawCSV.includes('"End Date"') + const containsEndTimeKey = rawCSV.includes('"End Time"') + const containsDurationHourKey = rawCSV.includes('"Duration (h)"') + const containsDurationDecimalKey = rawCSV.includes('"Duration (decimal)"') + + return ( + containsProjectKey && + containsDescriptionKey && + containsTaskKey && + containsUserKey && + containsStartDateKey && + containsStartTimeKey && + containsEndDateKey && + containsEndTimeKey && + containsDurationHourKey && + containsDurationDecimalKey + ) } SingleFileUpload.propTypes = { diff --git a/src/components/TimelineAreaChart.jsx b/src/components/TimelineAreaChart.jsx index 1465736..ae90bc7 100644 --- a/src/components/TimelineAreaChart.jsx +++ b/src/components/TimelineAreaChart.jsx @@ -4,8 +4,8 @@ import HighchartsReact from "highcharts-react-official" import { fileContent } from "./SingleFileUpload" // Date, Map -var projectDataMap = new Map(); -var allSeriesEnabled = true; +var projectDataMap = new Map() +var allSeriesEnabled = true export default function AreaChart() { const chartComponent = useRef(null) diff --git a/src/index.js b/src/index.js index 3326a6e..44a55c5 100644 --- a/src/index.js +++ b/src/index.js @@ -3,17 +3,17 @@ import ReactDOM from "react-dom/client" import "./index.css" import TimelineAreaChart from "./components/TimelineAreaChart" import TotalPieChart from "./components/TotalPieChart" -import SingleFileUploader, {fileContent} from "./components/SingleFileUpload" +import SingleFileUploader, { fileContent } from "./components/SingleFileUpload" import { GeistProvider, CssBaseline, Tabs } from "@geist-ui/core" export default function App() { const [fileUploaded, setFileUploaded] = useState(false) const handleUploadedFile = function () { - if(Array.isArray(fileContent)) { - setFileUploaded(true); + if (Array.isArray(fileContent)) { + setFileUploaded(true) } else { - setFileUploaded(false); + setFileUploaded(false) } } @@ -23,6 +23,13 @@ export default function App() { +
+
+

How to upload a file

+

+ Go to Clockify → Reports → Summary → Time Report (Detailed) → Export (As + a CSV file) +