Addition.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. import React, { useState, useEffect, useRef } from "react";
  2. import DataGrid, {Column} from "devextreme-react/data-grid";
  3. import {Alert} from "./AlertDialog.js";
  4. import {addNumbersToGrid, numbersToArrOfArr, afterCommaLen} from "./helpers.js";
  5. import "./App.css";
  6. let imdtRes;
  7. let imdtResIdx;
  8. let resArr = [];
  9. let noOfDigits = 0;
  10. function Addition() {
  11. const [input, setInput] = useState(""); // initial user input like 34+56.7
  12. const [realResult, setRealResult] = useState(0); // real result of calculation
  13. const [carryArr, setCarryArr] = useState([]); // array of carries ["1","0"]
  14. const [commaIdx, setCommaIdx] = useState(0); // int: comma position
  15. const [numbers, setNumbers] = useState(0); // array of arrays with input numbers
  16. const [showAlert, setShowAlert] = useState(false);
  17. const [numbersGrid, setNumbersGrid] = useState([{id: 1, number: ""}]);
  18. const [resultsGrid, setResultsGrid] = useState([{id: 1, number: ""}]);
  19. const [stepsGrid, setStepsGrid] = useState([{id: 1, step: ""}]);
  20. // focus the input field
  21. let calculationInput = useRef(null);
  22. useEffect(()=>{
  23. if(calculationInput.current && calculationInput.current.value === ""){
  24. calculationInput.current.focus();
  25. }
  26. },[])
  27. const handleInput = (e) => {
  28. setInput(e.target.value);
  29. }
  30. const handleResChange = (e, nosLeft) => {
  31. if(typeof e === "string"){
  32. imdtRes = e;
  33. }else{
  34. imdtRes = e.target.value;
  35. }
  36. // remove deleted input
  37. if(imdtRes === ""){
  38. resArr.shift();
  39. }else{
  40. // add input number to result array
  41. resArr.unshift(imdtRes);
  42. }
  43. }
  44. const handleCarryChange = (e, noOfDigits, nosLeft) => {
  45. let carryArrCopy = [...carryArr];
  46. carryArrCopy.unshift(e.target.value);
  47. setCarryArr(carryArrCopy);
  48. let noCarry = carryArrCopy[0] === "0" || carryArrCopy[0] === undefined
  49. if((nosLeft === 1 && noCarry) ||
  50. // if stop after first iteration, numbers left is undefined at first
  51. (nosLeft !== nosLeft && realResult.toString().length === 1 && noCarry)){
  52. nosLeft = 0;
  53. handleResChange(" ", 0)
  54. }
  55. if(nosLeft === 0){
  56. createIdmtResults()
  57. setShowAlert(true);
  58. }
  59. }
  60. const handleSubmit = (e) => {
  61. startCalculation();
  62. e.preventDefault(); // avoid page reload
  63. }
  64. const startCalculation = () => {
  65. if (input.includes("+")){
  66. let numbers = input.split("+").map(x => parseFloat(x.replace(",",".")));
  67. let realRes = numbers.reduce((x,y) => x+y, 0);
  68. let afterComma = Math.max(...numbers.map(x => afterCommaLen(x)));
  69. realRes = parseFloat(realRes.toFixed(afterComma));
  70. setRealResult(realRes);
  71. console.log("real result: ", realRes);
  72. let [numbersArr, commaIdx] = numbersToArrOfArr(numbers);
  73. setNumbers(numbersArr);
  74. setCommaIdx(commaIdx);
  75. setNumbersGrid(addNumbersToGrid(numbersArr, "+"));
  76. }
  77. }
  78. const ResultCarryForm = ({handleResChange, handleCarryChange}) => {
  79. let resInputField = useRef(null);
  80. let carryInputField = useRef(null);
  81. let labelText = "";
  82. let nosLeft = imdtResIdx + 1;
  83. let carryText = "Übertrag = ";
  84. if(typeof numbers === "object" && imdtResIdx !== -1){
  85. // set the digit index, start with the last digit
  86. if(typeof imdtResIdx === "undefined" || imdtResIdx === null){
  87. noOfDigits = Math.min(...numbers.map(n => n.length));
  88. imdtResIdx = noOfDigits - 1;
  89. }
  90. // skip over comma
  91. if(imdtResIdx === commaIdx){
  92. if(!resArr.includes(".")){
  93. handleResChange(".", nosLeft);
  94. }
  95. imdtResIdx = imdtResIdx - 1;
  96. nosLeft = nosLeft - 1;
  97. }
  98. // iterate numbers for this digit index
  99. for (let n in numbers){
  100. // skip last empty number if no carry
  101. if(nosLeft === 1 && carryArr[0] === "0"){
  102. nosLeft = 0;
  103. imdtResIdx = -1;
  104. return <></>
  105. }
  106. // create label text like: d + d + .. = input
  107. let digit = numbers[parseInt(n)][imdtResIdx];
  108. if(digit === "" || digit === "&nbsp;"){
  109. digit = 0;
  110. }
  111. labelText += digit;
  112. if (parseInt(n) === numbers.length - 1){
  113. if(carryArr[0] !== undefined && carryArr[0] > 0){
  114. labelText += " + " + carryArr[0].toString();
  115. }
  116. labelText += " = ";
  117. }else{
  118. labelText += " + ";
  119. }
  120. }
  121. imdtResIdx -= 1;
  122. nosLeft -= 1;
  123. return (
  124. <form>
  125. <label htmlFor="input_result" id="input_result_label">
  126. {labelText}
  127. </label>
  128. <input
  129. onChange={(e) => handleResChange(e, nosLeft)}
  130. type="text" id="input_result" size="2" maxLength="1"
  131. aria-labelledby="input_result_label" aria-required="true"
  132. ref={resInputField} autoFocus/>
  133. <br/>
  134. <label htmlFor="input_carry" id="input_carry_label">
  135. {carryText}
  136. </label>
  137. <input
  138. onChange={(e) => handleCarryChange(e, noOfDigits, nosLeft)}
  139. type="text" id="input_carry" size="2" maxLength="1"
  140. aria-labelledby="input_carry_label" aria-required="true"
  141. ref={carryInputField}/>
  142. </form>
  143. );
  144. }else{
  145. return (
  146. <>
  147. </>
  148. );
  149. }
  150. }
  151. const addButtonsToImdtSteps = () => {
  152. if(stepsGrid[0]["step"]!==""){
  153. let table = document.getElementById("idmtResultSteps").getElementsByTagName("table")[0]
  154. let trs = table.getElementsByTagName("tr");
  155. for(let trIdx in trs){
  156. let tr = trs[trIdx];
  157. if(tr instanceof HTMLElement && trIdx < trs.length-1){
  158. let td = tr.getElementsByTagName("td")[0];
  159. let btn = document.createElement("button");
  160. btn.innerHTML = "hier starten";
  161. btn.classList = "btn btn-secondary btn-sm";
  162. btn.id = "btn-" + trIdx;
  163. btn.addEventListener("click", () => startOver(trIdx));
  164. td.appendChild(btn);
  165. }
  166. }
  167. }
  168. }
  169. const showIdmtResults = () => {
  170. setShowAlert(false);
  171. document.getElementById("idmtResultSteps").style.display = "inline-block";
  172. addButtonsToImdtSteps()
  173. }
  174. const createIdmtResults = () => {
  175. let realResArr = realResult.toString().split("");
  176. while(realResArr.length < resArr.length){
  177. realResArr.unshift("&nbsp;"); // add " " before
  178. }
  179. let carries = carryArr.map(x => x || "0")
  180. carries.push("0");
  181. while(carries.length < resArr.length){
  182. carries.unshift("0"); // add "0" before
  183. }
  184. let foundComma = false;
  185. let stepsGridCopy = [];
  186. for (let i=0; i<resArr.length; i++) {
  187. let text = "";
  188. let trueNumbers = false;
  189. let idxNumbers = resArr.length - i - 1;
  190. let idxCarry = carries.length - i - 1;
  191. let realSum = 0;
  192. for (let j=0; j<numbers.length; j++) {
  193. let no = numbers[j][idxNumbers];
  194. if (no!=="." && no!=="&nbsp;"){
  195. trueNumbers = true;
  196. realSum += parseInt(no);
  197. }
  198. text += no
  199. if (j<numbers.length-1){
  200. text += " + "
  201. }
  202. }
  203. // ignore indexes without real digits
  204. if (resArr[idxNumbers]!=="&nbsp;" || (carries[idxCarry]!=="&nbsp;" & carries[idxCarry]!=="0")){
  205. trueNumbers = true;
  206. }
  207. // carry array has no "."
  208. if (resArr[idxNumbers] === "."){
  209. foundComma = true;
  210. trueNumbers = false;
  211. }
  212. if (foundComma){
  213. idxCarry += 1;
  214. }
  215. // add carry only if > 0
  216. if (carries[idxCarry]!=="0"){
  217. realSum += parseInt(carries[idxCarry]);
  218. text += " + " + carries[idxCarry] // + " Übertrag"
  219. }
  220. let realCarry = parseInt(realSum/10).toString()
  221. realSum = (realSum % 10).toString()
  222. text += " = "
  223. if (trueNumbers){
  224. text += resArr[idxNumbers]
  225. text += " und " + carries[idxCarry-1] + " Übertrag "
  226. text = text.replace(/&nbsp;/g, "0")
  227. text += resArr[idxNumbers]===realSum && carries[idxCarry-1]===realCarry ? "(Richtig) " : "(Falsch) ";
  228. stepsGridCopy.push({step: text});
  229. }
  230. }
  231. document.getElementById("stepsParagraph").innerHTML = "Rechenschritte: ";
  232. setStepsGrid(stepsGridCopy);
  233. let btnSubmit = document.createElement("button");
  234. btnSubmit.innerHTML = "Ergebnis abgeben";
  235. btnSubmit.addEventListener("click", finishCalculation);
  236. btnSubmit.classList = "btn btn-secondary btn-sm";
  237. btnSubmit.id = "btnSubmitSteps";
  238. document.getElementById("idmtResultSteps").appendChild(btnSubmit);
  239. document.getElementById("idmtResultSteps").tabIndex = 0;
  240. document.getElementById("idmtResultSteps").focus();
  241. document.getElementById("idmtResultSteps").style.display = "none";
  242. }
  243. const startOver = (idx) => {
  244. let invertedIdx = numbers[0].length - idx - 1;
  245. imdtResIdx = invertedIdx;
  246. let carryArrCopy = [...carryArr];
  247. let slicer = invertedIdx;
  248. if(resArr[0] !== "&nbsp;" && !resArr.includes(".")){
  249. slicer += 1;
  250. }else if(resArr[0] === "&nbsp;" && resArr.includes(".")){
  251. slicer -= 1;
  252. }
  253. carryArrCopy = carryArrCopy.slice(slicer);
  254. // reset result and carries until the chosen step
  255. let commaPos = resArr.indexOf(".");
  256. if(resArr.includes(".") && commaPos >= invertedIdx){
  257. imdtResIdx -= 1;
  258. resArr = resArr.slice(invertedIdx);
  259. }else{
  260. resArr = resArr.slice(invertedIdx + 1);
  261. }
  262. setCarryArr(carryArrCopy);
  263. document.getElementById("btnSubmitSteps").remove();
  264. document.getElementById("idmtResultSteps").style.display = "none";
  265. }
  266. const finishCalculation = () => {
  267. setShowAlert(false);
  268. document.getElementById("idmtResultSteps").innerHTML = "";
  269. let resCalc = resArr.filter(n => n !== "&nbsp;").join("");
  270. setResultsGrid([{number: resCalc}]);
  271. resCalc = parseFloat(resCalc);
  272. let message = ""
  273. if(resCalc === realResult){
  274. message = "Richtig!"
  275. }else{
  276. message = "Das ist leider falsch."
  277. }
  278. message = "<p>" + message + "</p>"
  279. document.getElementById("finishCalculation").innerHTML = message
  280. }
  281. return (
  282. <main>
  283. <h1>Addition</h1>
  284. <form onSubmit={(e) => handleSubmit(e)}>
  285. <label htmlFor="calculationInput">
  286. Rechnung:
  287. </label>
  288. <input
  289. type="text"
  290. id="calculationInput"
  291. onChange={(e) => handleInput(e)}
  292. aria-label="Rechnung"
  293. aria-required="true"
  294. ref={calculationInput}/>
  295. <input type="submit" value="berechnen"/>
  296. </form>
  297. <div id="overview">
  298. <DataGrid
  299. dataSource={numbersGrid}
  300. keyExpr="id"
  301. focusedRowEnabled={true}
  302. showBorders={true}
  303. showColumnHeaders={false}
  304. >
  305. <Column dataField="number" />
  306. </DataGrid>
  307. <hr></hr>
  308. <DataGrid
  309. dataSource={resultsGrid}
  310. focusedRowEnabled={true}
  311. showBorders={true}
  312. showColumnHeaders={false}
  313. >
  314. <Column dataField="number" />
  315. </DataGrid>
  316. </div>
  317. <div id="calculation">
  318. <ResultCarryForm
  319. handleResChange={handleResChange}
  320. handleCarryChange={handleCarryChange}/>
  321. </div>
  322. <div id="finishCalculation"></div>
  323. <div id="idmtResultSteps">
  324. <p id="stepsParagraph"></p>
  325. <DataGrid
  326. dataSource={stepsGrid}
  327. focusedRowEnabled={true}
  328. showBorders={true}
  329. showColumnHeaders={false}
  330. noDataText=""
  331. >
  332. <Column dataField="step" />
  333. </DataGrid>
  334. </div>
  335. <Alert
  336. showAlert={showAlert}
  337. finishCalc={finishCalculation}
  338. showIdmtResults={showIdmtResults}
  339. />
  340. </main>
  341. );
  342. }
  343. export default Addition;