diff --git a/esl/README.md b/esl/README.md
new file mode 100644
index 0000000..6336155
--- /dev/null
+++ b/esl/README.md
@@ -0,0 +1,7 @@
+Sort Class by Level
+-------------------
+
+
+A [Pen](https://codepen.io/devkdo/pen/MWNyWKz) by [dvkdo](https://codepen.io/devkdo) on [CodePen](https://codepen.io).
+
+[License](https://codepen.io/license/pen/MWNyWKz).
\ No newline at end of file
diff --git a/esl/constants.js b/esl/constants.js
new file mode 100644
index 0000000..3dbf855
--- /dev/null
+++ b/esl/constants.js
@@ -0,0 +1,21 @@
+
+const constants = {
+ Q_NAME: "Nombre y Apellidos - Nomes e Sobrenomes - First and Last Names",
+ Q_EMAIL: "Email Address",
+ Q_PHONE: "Número de Teléfono - Número de Telefone - Phone Number",
+ Q_LEVEL:
+ "¿Cuál es su nivel de inglés? Qual é o seu nível de inglês? What is your level of English?",
+ Q_DAY:
+ "Which night do you prefer? ¿Qué noche prefiere? Qual noite você prefere?",
+
+ A_LEVEL1:
+ "I don't speak or understand any English. No hablo ni entiendo nada de inglés. Eu não falo nem entendo nenhum inglês.",
+ A_LEVEL2:
+ "I speak and understand a little bit of English. Hablo y entiendo un poco de inglés. Falo e entendo um pouco de inglês.",
+ A_LEVEL3:
+ "I am comfortable having short conversations in English. Estoy cómodo para tener conversaciones cortas en inglés. Sinto-me confortável em conversas curtas em inglês.",
+ A_DAYMON: "Monday - lunes - segunda-feira",
+ A_DAYTUE: "Tuesday - martes - terça-feira"
+};
+
+export default constants;
\ No newline at end of file
diff --git a/esl/favicon.ico b/esl/favicon.ico
new file mode 100644
index 0000000..f1e7116
Binary files /dev/null and b/esl/favicon.ico differ
diff --git a/format_contacts.sh b/esl/format_contacts.sh
similarity index 100%
rename from format_contacts.sh
rename to esl/format_contacts.sh
diff --git a/esl/index.html b/esl/index.html
new file mode 100644
index 0000000..e9921eb
--- /dev/null
+++ b/esl/index.html
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+ Class Sort by Level
+
+
+
+
+
+
+
+
+
+
Upload XLS or XLSX file of Student Registration
+
+
+
+ Show Classes
+ Show Mailing Lists
+ Show Student Info
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/esl/script.js b/esl/script.js
new file mode 100644
index 0000000..8e133c0
--- /dev/null
+++ b/esl/script.js
@@ -0,0 +1,562 @@
+import constants from './constants.js';
+
+document.getElementById("btn-sort").addEventListener("click", processXLSClasses);
+document.getElementById("btn-emails").addEventListener("click", processXLSEmails);
+document.getElementById("btn-students").addEventListener("click", processXLSEmailsAndPhone);
+// document.getElementById("btn-dl").addEventListener("click", processAttendance);
+
+function callProcess(){
+ resetOutput();
+ const fileInput = document.getElementById("input-file");
+ const { mondays, tuesdays } = processXLS(fileInput);
+ displayLists(mondays.beg, mondays.nonbeg, tuesdays.beg, tuesdays.nonbeg);
+ console.log("result", mondays, tuesdays);
+}
+
+function processXLS(fileInput) {
+ const file = fileInput.files[0];
+
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = function (e) {
+ const fileData = new Uint8Array(e.target.result);
+ const workbook = XLSX.read(fileData, { type: "array" });
+ const sheetName = workbook.SheetNames[0];
+ const worksheet = workbook.Sheets[sheetName];
+ const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
+ const headers = jsonData[0];
+
+ const splitData = jsonData.slice(1).map((row) => {
+ let obj = {};
+ headers.forEach((header, index) => {
+ if (
+ header === constants.Q_NAME ||
+ header === constants.Q_EMAIL ||
+ header === constants.Q_DAY ||
+ header === constants.Q_LEVEL
+ ) {
+ obj[header] = row[index];
+ }
+ });
+ return obj;
+ });
+
+ const mon1 = [], mon2 = [], monAny = [];
+ const tue1 = [], tue2 = [], tueAny = [];
+
+ splitData.forEach((student) => {
+ const name = student[constants.Q_NAME];
+ const email = student[constants.Q_EMAIL];
+ const day = student[constants.Q_DAY];
+ const level = student[constants.Q_LEVEL];
+ const studentInfo = { name, email };
+
+ switch (day) {
+ case constants.A_DAYMON:
+ if (level === constants.A_LEVEL1) mon1.push(studentInfo);
+ else if (level === constants.A_LEVEL3) mon2.push(studentInfo);
+ else monAny.push(studentInfo);
+ break;
+ case constants.A_DAYTUE:
+ if (level === constants.A_LEVEL1) tue1.push(studentInfo);
+ else if (level === constants.A_LEVEL3) tue2.push(studentInfo);
+ else tueAny.push(studentInfo);
+ break;
+ default:
+ break;
+ }
+ });
+
+ const mondays = evenDistribute(monAny, mon1, mon2);
+ const tuesdays = evenDistribute(tueAny, tue1, tue2);
+
+ // Return the lists for Mondays and Tuesdays
+ return { mondays, tuesdays };
+ };
+ reader.readAsArrayBuffer(file);
+ } else {
+ alert("Please upload an XLS/XLSX file.");
+ }
+}
+
+function processXLSEmails() {
+ resetOutput();
+ const fileInput = document.getElementById("input-file");
+ const file = fileInput.files[0];
+
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = function (e) {
+ const fileData = new Uint8Array(e.target.result);
+ const workbook = XLSX.read(fileData, { type: "array" });
+ const sheetName = workbook.SheetNames[0];
+ const worksheet = workbook.Sheets[sheetName];
+ const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
+
+ const headers = jsonData[0];
+ const splitData = jsonData.slice(1).map((row) => {
+ let obj = {};
+ headers.forEach((header, index) => {
+ if (
+ header === constants.Q_NAME ||
+ header === constants.Q_EMAIL ||
+ header === constants.Q_DAY ||
+ header === constants.Q_LEVEL
+ ) {
+ obj[header] = row[index];
+ }
+ });
+ return obj;
+ });
+
+ const mon1 = [];
+ const mon2 = [];
+ const monAny = [];
+ const tue1 = [];
+ const tue2 = [];
+ const tueAny = [];
+
+ splitData.forEach((student) => {
+ const name = student[constants.Q_NAME];
+ const email = validateAndFixEmail(student[constants.Q_EMAIL]);
+ const day = student[constants.Q_DAY];
+ const level = student[constants.Q_LEVEL];
+ const studentInfo = { name, email };
+
+ switch (day) {
+ case constants.A_DAYMON:
+ if (level === constants.A_LEVEL1) mon1.push(studentInfo);
+ else if (level === constants.A_LEVEL3) mon2.push(studentInfo);
+ else monAny.push(studentInfo);
+ break;
+
+ case constants.A_DAYTUE:
+ if (level === constants.A_LEVEL1) tue1.push(studentInfo);
+ else if (level === constants.A_LEVEL3) tue2.push(studentInfo);
+ else tueAny.push(studentInfo);
+ break;
+
+ default:
+ break;
+ }
+ });
+ const mondays = evenDistribute(monAny, mon1, mon2);
+ const tuesdays = evenDistribute(tueAny, tue1, tue2);
+ displayEmails(mondays.beg, mondays.nonbeg, tuesdays.beg, tuesdays.nonbeg);
+ // createExcel(mondays.beg, mondays.nonbeg, tuesdays.beg, tuesdays.nonbeg);
+
+ // result = (new Array([mondays, tuesdays]));
+ // console.log("result", result);
+ };
+ reader.readAsArrayBuffer(file);
+
+ } else {
+ alert("Please upload an XLS/XLSX file.");
+ }
+}
+function processXLSEmailsAndPhone() {
+ resetOutput();
+ const fileInput = document.getElementById("input-file");
+ const file = fileInput.files[0];
+
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = function (e) {
+ const fileData = new Uint8Array(e.target.result);
+ const workbook = XLSX.read(fileData, { type: "array" });
+ const sheetName = workbook.SheetNames[0];
+ const worksheet = workbook.Sheets[sheetName];
+ const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
+
+ const headers = jsonData[0];
+ const splitData = jsonData.slice(1).map((row) => {
+ let obj = {};
+ headers.forEach((header, index) => {
+ if (
+ header === constants.Q_NAME ||
+ header === constants.Q_EMAIL ||
+ header === constants.Q_PHONE ||
+ header === constants.Q_DAY ||
+ header === constants.Q_LEVEL
+ ) {
+ obj[header] = row[index];
+ }
+ });
+ return obj;
+ });
+
+ const mon1 = [];
+ const mon2 = [];
+ const monAny = [];
+ const tue1 = [];
+ const tue2 = [];
+ const tueAny = [];
+
+ splitData.forEach((student) => {
+ const name = student[constants.Q_NAME];
+ const email = validateAndFixEmail(student[constants.Q_EMAIL]);
+ const phone = validateAndFixPhone( student[constants.Q_PHONE]);
+ const day = student[constants.Q_DAY];
+ const level = student[constants.Q_LEVEL];
+ const studentInfo = { name, email, phone };
+
+ switch (day) {
+ case constants.A_DAYMON:
+ if (level === constants.A_LEVEL1) mon1.push(studentInfo);
+ else if (level === constants.A_LEVEL3) mon2.push(studentInfo);
+ else monAny.push(studentInfo);
+ break;
+
+ case constants.A_DAYTUE:
+ if (level === constants.A_LEVEL1) tue1.push(studentInfo);
+ else if (level === constants.A_LEVEL3) tue2.push(studentInfo);
+ else tueAny.push(studentInfo);
+ break;
+
+ default:
+ break;
+ }
+ });
+ const mondays = evenDistribute(monAny, mon1, mon2);
+ const tuesdays = evenDistribute(tueAny, tue1, tue2);
+ displayStudentInfo(mondays.beg, mondays.nonbeg, tuesdays.beg, tuesdays.nonbeg);
+ // createExcel(mondays.beg, mondays.nonbeg, tuesdays.beg, tuesdays.nonbeg);
+
+ // result = (new Array([mondays, tuesdays]));
+ // console.log("result", result);
+ };
+ reader.readAsArrayBuffer(file);
+
+ } else {
+ alert("Please upload an XLS/XLSX file.");
+ }
+}
+function processXLSClasses() {
+ resetOutput();
+ const fileInput = document.getElementById("input-file");
+ const file = fileInput.files[0];
+
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = function (e) {
+ const fileData = new Uint8Array(e.target.result);
+ const workbook = XLSX.read(fileData, { type: "array" });
+ const sheetName = workbook.SheetNames[0];
+ const worksheet = workbook.Sheets[sheetName];
+ const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
+
+ const headers = jsonData[0];
+ const splitData = jsonData.slice(1).map((row) => {
+ let obj = {};
+ headers.forEach((header, index) => {
+ if (
+ header === constants.Q_NAME ||
+ header === constants.Q_EMAIL ||
+ header === constants.Q_DAY ||
+ header === constants.Q_LEVEL
+ ) {
+ obj[header] = row[index];
+ }
+ });
+ return obj;
+ });
+
+ const mon1 = [];
+ const mon2 = [];
+ const monAny = [];
+ const tue1 = [];
+ const tue2 = [];
+ const tueAny = [];
+
+ splitData.forEach((student) => {
+ const name = student[constants.Q_NAME];
+ const email = student[constants.Q_EMAIL];
+ const day = student[constants.Q_DAY];
+ const level = student[constants.Q_LEVEL];
+ const studentInfo = { name, email };
+
+ switch (day) {
+ case constants.A_DAYMON:
+ if (level === constants.A_LEVEL1) mon1.push(studentInfo);
+ else if (level === constants.A_LEVEL3) mon2.push(studentInfo);
+ else monAny.push(studentInfo);
+ break;
+
+ case constants.A_DAYTUE:
+ if (level === constants.A_LEVEL1) tue1.push(studentInfo);
+ else if (level === constants.A_LEVEL3) tue2.push(studentInfo);
+ else tueAny.push(studentInfo);
+ break;
+
+ default:
+ break;
+ }
+ });
+ const mondays = evenDistribute(monAny, mon1, mon2);
+ const tuesdays = evenDistribute(tueAny, tue1, tue2);
+ displayLists(mondays.beg, mondays.nonbeg, tuesdays.beg, tuesdays.nonbeg);
+ // createExcel(mondays.beg, mondays.nonbeg, tuesdays.beg, tuesdays.nonbeg);
+ };
+ reader.readAsArrayBuffer(file);
+
+ } else {
+ alert("Please upload an XLS/XLSX file.");
+ }
+}
+function evenDistribute(anyList, l1, l2) {
+ if (anyList.length == 0) return;
+
+ const list1 = l1.slice();
+ const list2 = l2.slice();
+ const listAny = anyList.slice();
+ const lengthListAny = listAny.length;
+
+ console.log(`Registered: beginner (${list1.length}), intermediate (${listAny.length}), advanced (${list2.length})`);
+
+ let loop = 0;
+ let done = false;
+ while (!done || listAny.length > 0) {
+ // default: case 0: same length
+ // split listany and add half to each
+
+ // case 1: first list is bigger
+ // splice listany and add difference to second list
+ // split remaining listany between both lists
+
+ // case 2: second list is bigger
+ // splice listany and add difference to first list
+ // split remaining listany between both lists
+
+ let diff = list1.length - list2.length;
+ let maxIndex =
+ Math.abs(diff) < listAny.length ? Math.abs(diff) - 1 : listAny.length - 1;
+ if (maxIndex == 0) maxIndex = 1;
+ // console.log("diff", diff);
+ // console.log("maxIndex", maxIndex);
+
+ if (diff > 0) {
+ list2.push(...listAny.splice(0, maxIndex));
+ } else if (diff < 0) {
+ list1.push(...listAny.splice(0, maxIndex));
+ } else {
+ list1.push(...listAny.splice(0, listAny.length / 2));
+ list2.push(...listAny.splice(0, listAny.length));
+ done = true;
+ }
+ loop++;
+ }
+
+ console.log(`Distributed: beginner (${list1.length}), intermediate (${listAny.length}), nonbeginner (${list2.length})`);
+
+ return { beg: list1, nonbeg: list2 };
+}
+
+function getEmailString(list){
+ let emails = "";
+ for(let student of list.values()){
+ const entry = `"${student.name}"<${student.email}>,`;
+ emails += entry;
+ }
+ return emails;
+}
+function getStudentInfoString(list){
+ let names = "";
+ let emails = "";
+ let numbers = "";
+ let infoString = "";
+ for(let student of list.values()){
+ const entry = `${student.name}, ${student.email}, ${student.phone} `;
+ names += "\n" + student.name;
+ emails += "\n" + student.email;
+ numbers += "\n" + student.phone;
+ infoString += entry;
+ }
+ console.log(names);
+ console.log(emails);
+ console.log(numbers);
+ console.log(infoString);
+ return infoString;
+}
+
+function displayStudentInfo(l1, l2, l3, l4) {
+ const outputContainer = document.getElementById("output-container");
+ outputContainer.classList.add("section-green");
+
+ const outputHeaders1 = document.getElementById("output-headers1");
+ const outputHeaders2 = document.getElementById("output-headers2");
+ const outputHeaders3 = document.getElementById("output-headers3");
+ const outputHeaders4 = document.getElementById("output-headers4");
+ const output1 = document.getElementById("output1");
+ const output2 = document.getElementById("output2");
+ const output3 = document.getElementById("output3");
+ const output4 = document.getElementById("output4");
+ outputHeaders1.innerHTML = `Student Info Monday Beginner (${l1.length}) `;
+ outputHeaders2.innerHTML = `Monday NonBeginner (${l2.length}) `;
+ outputHeaders3.innerHTML = `Tuesday Beginner (${l3.length}) `;
+ outputHeaders4.innerHTML = `Tuesday NonBeginner (${l4.length}) `;
+ console.log(outputHeaders1.textContent);
+ output1.innerHTML = getStudentInfoString(l1);
+ console.log(outputHeaders2.textContent);
+ output2.innerHTML = getStudentInfoString(l2);
+ console.log(outputHeaders3.textContent);
+ output3.innerHTML = getStudentInfoString(l3);
+ console.log(outputHeaders4.textContent);
+ output4.innerHTML = getStudentInfoString(l4);
+}
+function displayEmails(l1, l2, l3, l4) {
+ const outputContainer = document.getElementById("output-container");
+ outputContainer.classList.add("section-green");
+
+ const outputHeaders1 = document.getElementById("output-headers1");
+ const outputHeaders2 = document.getElementById("output-headers2");
+ const outputHeaders3 = document.getElementById("output-headers3");
+ const outputHeaders4 = document.getElementById("output-headers4");
+ const output1 = document.getElementById("output1");
+ const output2 = document.getElementById("output2");
+ const output3 = document.getElementById("output3");
+ const output4 = document.getElementById("output4");
+ outputHeaders1.innerHTML = `Mailing List Monday Beginner (${l1.length}) `;
+ outputHeaders2.innerHTML = `Monday NonBeginner (${l2.length}) `;
+ outputHeaders3.innerHTML = `Tuesday Beginner (${l3.length}) `;
+ outputHeaders4.innerHTML = `Tuesday NonBeginner (${l4.length}) `;
+ output1.textContent += getEmailString(l1);
+ output2.textContent += getEmailString(l2);
+ output3.textContent += getEmailString(l3);
+ output4.textContent += getEmailString(l4);
+}
+
+function displayLists(l1, l2, l3, l4) {
+ const outputContainer = document.getElementById("output-container");
+ outputContainer.classList.add("section-green");
+
+ const outputHeaders1 = document.getElementById("output-headers1");
+ const outputHeaders2 = document.getElementById("output-headers2");
+ const outputHeaders3 = document.getElementById("output-headers3");
+ const outputHeaders4 = document.getElementById("output-headers4");
+ const output1 = document.getElementById("output1");
+ const output2 = document.getElementById("output2");
+ const output3 = document.getElementById("output3");
+ const output4 = document.getElementById("output4");
+ outputHeaders1.innerHTML = `Monday Beginner (${l1.length}) `;
+ output1.innerHTML = `${JSON.stringify(l1,null,2)} `;
+ outputHeaders2.innerHTML = `Monday Non-Beginner (${l2.length}) `;
+ output2.innerHTML = `${JSON.stringify( l2, null, 2 )} `;
+ outputHeaders3.innerHTML = `Tuesday Beginner (${l3.length}) `;
+ output3.innerHTML = `${JSON.stringify( l3, null, 2 )} `
+ outputHeaders4.innerHTML = `Tuesday Non-Beginner (${l4.length}) `;
+ output4.innerHTML = `${JSON.stringify(l4,null,2)} `;
+}
+
+function resetOutput(){
+ // const outputContainer = document.getElementById("output-container");
+ // outputContainer.classList.remove("section-green");
+
+ const outputHeaders1 = document.getElementById("output-headers1");
+ const outputHeaders2 = document.getElementById("output-headers2");
+ const outputHeaders3 = document.getElementById("output-headers3");
+ const outputHeaders4 = document.getElementById("output-headers4");
+ const output1 = document.getElementById("output1");
+ const output2 = document.getElementById("output2");
+ const output3 = document.getElementById("output3");
+ const output4 = document.getElementById("output4");
+ outputHeaders1.innerHTML = ``;
+ outputHeaders2.innerHTML = ``;
+ outputHeaders3.innerHTML = ``;
+ outputHeaders4.innerHTML = ``;
+ output1.textContent = ``;
+ output2.textContent = ``;
+ output3.textContent = ``;
+ output4.textContent = ``;
+}
+
+
+function createExcel(data1, data2, data3, data4) {
+ // Create a new workbook
+ const workbook = XLSX.utils.book_new();
+
+ // Create worksheets for each data array
+ createWorksheet(data1, 'Class1');
+ createWorksheet(data2, 'Class2');
+ createWorksheet(data3, 'Class3');
+ createWorksheet(data4, 'Class4');
+
+ const wbout = XLSX.write(workbook, { bookType: 'xlsx', type: 'binary' });
+
+ const blob = new Blob([s2ab(wbout)], { type: 'application/octet-stream' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'attendance.xlsx';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+
+ // Write the workbook to a file
+ // XLSX.writeFile(workbook, 'attendance.xlsx');
+}
+
+ function createWorksheet(data, sheetName) {
+ const worksheetData = data.map(item => [item.name, item.email]);
+ const worksheet = XLSX.utils.aoa_to_sheet([['Name', 'Email'], ...worksheetData]);
+ XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
+ }
+
+function s2ab(s) {
+ const buf = new ArrayBuffer(s.length);
+ const view = new Uint8Array(buf); // view the buffer as array of 8-bit unsigned int
+ for (let i = 0; i < s.length; i++) {
+ view[i] = s.charCodeAt(i) & 0xFF; // Assign each character's code to the buffer
+ }
+ return buf;
+}
+function validateAndFixEmail(email) {
+ // Regular expression to validate email format
+ const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+
+ // Function to fix common domain typos
+ function fixDomainTypos(email) {
+ // List of common typos and their corrections
+ const typoCorrections = {
+ 'or': 'org',
+ 'cmo': 'com',
+ 'con': 'com',
+ 'co': 'com',
+ 'cm': 'com'
+ };
+
+ return email.replace(/(@[^\s@]+\.)[^@]+$/, function(match, domain) {
+ return domain + (typoCorrections[match.slice(domain.length)] || match.slice(domain.length));
+ });
+ }
+
+ // Fix domain typos
+ let correctedEmail = fixDomainTypos(email);
+
+ // Validate corrected email
+ if (emailPattern.test(correctedEmail)) {
+ console.log('Email', email, 'Corrected Email:', correctedEmail);
+ return correctedEmail;
+ } else {
+ console.error('Invalid email format.', email);
+ return "invalid:" + email;
+ }
+}
+function validateAndFixPhone(phone) {
+ // Remove all non-digit characters
+ let digits = phone.toString().replace(/\D/g, '');
+
+ let correctedPhone = "";
+ // Format based on length and starting digit
+ if (digits.length === 10) {
+ correctedPhone = digits.replace(/(\d{3})(\d{3})(\d{4})/, '$1-$2-$3');
+ } else if (digits.length === 11 && digits.startsWith('1')) {
+ correctedPhone = digits.replace(/(\d{1})(\d{3})(\d{3})(\d{4})/, '$1-$2-$3-$4');
+ } else if (digits.length > 10) {
+ correctedPhone = digits.replace(/(\d+)(\d{3})(\d{3})(\d{4})/, '$1-$2-$3-$4');
+ } else {
+ console.error('Invalid phone format.', phone);
+ return "invalid:" + phone;
+ }
+ console.log('Phone', phone, 'Corrected Phone:', correctedPhone);
+ return correctedPhone;
+}
diff --git a/esl/styles.css b/esl/styles.css
new file mode 100644
index 0000000..99998e9
--- /dev/null
+++ b/esl/styles.css
@@ -0,0 +1,14 @@
+.container{
+ margin: 10px;
+ padding: 10px;
+}
+.section-green {
+ background-color: honeydew;
+ color: black;
+/* border: 2px solid black; */
+ margin: 20px;
+ padding: 20px;
+}
+.gap-bottom{
+ margin-bottom: 20px;
+}