Work Search Record CSV Upload

Adds a CSV Upload Button

  1. // ==UserScript==
  2. // @name Work Search Record CSV Upload
  3. // @namespace https://brightentompkins.com
  4. // @version 0.1
  5. // @description Adds a CSV Upload Button
  6. // @author vantaboard <brightenqtompkins@gmail.com>
  7. // @match https://uio.edd.ca.gov/UIO/Pages/ExternalUser/Certification/FormCCA4581RegularDUAWorkSearchRecord.aspx
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=ca.gov
  9. // @license MIT
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. function CSVToArray(strData, strDelimiter = ',') {
  14. const objPattern = new RegExp(
  15. `(\\${strDelimiter}|\\r?\\n|\\r|^)(?:"([^"]*(?:""[^"]*)*)"|([^"\\${strDelimiter}\\r\\n]*))`,
  16. 'gi'
  17. );
  18.  
  19. const arrData = [[]];
  20. let arrMatches = null;
  21.  
  22. while ((arrMatches = objPattern.exec(strData))) {
  23. const strMatchedDelimiter = arrMatches[1];
  24. if (
  25. strMatchedDelimiter.length &&
  26. strMatchedDelimiter !== strDelimiter
  27. ) {
  28. arrData.push([]);
  29. }
  30.  
  31. let strMatchedValue;
  32. if (arrMatches[2]) {
  33. strMatchedValue = arrMatches[2].replace(new RegExp('""', 'g'), '"');
  34. } else {
  35. strMatchedValue = arrMatches[3];
  36. }
  37.  
  38. arrData[arrData.length - 1].push(strMatchedValue);
  39. }
  40.  
  41. arrData.pop();
  42.  
  43. return arrData;
  44. }
  45.  
  46. function getLabel(input) {
  47. let element = input;
  48. while (element) {
  49. if (element.tagName === 'LABEL') {
  50. return element.innerText;
  51. }
  52.  
  53. if (element.querySelector('label')) {
  54. return [...element.querySelectorAll('label')].slice(-1)[0]
  55. .innerText;
  56. }
  57.  
  58. element = element.parentElement;
  59. }
  60. }
  61.  
  62. function fill(input, value) {
  63. console.log(`Setting ${getLabel(input).trim()} to ${value}`);
  64. input.value = value;
  65. input.dispatchEvent(new Event('change'));
  66. }
  67.  
  68. function flatArray(...args) {
  69. const arr = [];
  70.  
  71. args.forEach((arg) => {
  72. if (!arg) {
  73. return;
  74. }
  75.  
  76. Array.isArray(arg) ? arr.push(...arg) : arr.push(arg);
  77. });
  78.  
  79. return arr;
  80. }
  81.  
  82. function wrapInputId(
  83. token,
  84. suffix,
  85. prefix = [
  86. 'contentMain',
  87. 'contentMain',
  88. 'ucRegularDUA4581WorkSearchRecordV2',
  89. 'frmFormWorkSearchInformation',
  90. ],
  91. postToken = 'ctl00',
  92. delimiter = '_'
  93. ) {
  94. return `#${flatArray(prefix, token, postToken, suffix).join(delimiter)}`;
  95. }
  96.  
  97. const inputs = [
  98. {
  99. regex: new RegExp(/date.+contact/i),
  100. formatter: (value) =>
  101. new Intl.DateTimeFormat('en-US', {
  102. year: 'numeric',
  103. month: '2-digit',
  104. day: '2-digit',
  105. }).format(new Date(value)),
  106. suffix: 'txtDatePicker',
  107. token: 'prtDateOfContact',
  108. },
  109. {
  110. regex: new RegExp(/type.+work|work.+type/i),
  111. suffix: 'txtValue',
  112. token: 'prtTypeOfWork',
  113. },
  114. {
  115. regex: new RegExp(/employer.+agency|agency.+employer/i),
  116. suffix: 'txtValue',
  117. token: 'prtEmployerAgencyName',
  118. },
  119. {
  120. regex: new RegExp(/contact.+type|type.+contact/i),
  121. formatter: (value) =>
  122. ({
  123. Mail: 6656,
  124. Fax: 6657,
  125. Email: 6658,
  126. 'In-Person': 6659,
  127. Online: 6660,
  128. Phone: 6661,
  129. }[String(value).trim()]),
  130. suffix: 'ddlValue',
  131. token: 'prtContactType',
  132. },
  133. {
  134. regex: new RegExp(/outcome.+contact|contact.+outcome/i),
  135. formatter: (value) =>
  136. ({
  137. Applied: 6662,
  138. 'No Decision': 6663,
  139. Hired: 6664,
  140. 'Not Hiring': 6665,
  141. Pending: 6666,
  142. Interviewed: 6667,
  143. 'Interview Date Set': 6668,
  144. 'No response from employer': 6669,
  145. }[String(value).trim()]),
  146. suffix: 'ddlValue',
  147. token: 'prtOutcomeWorkInquiry',
  148. },
  149. {
  150. regex: new RegExp(/person.+contacted|contacted.+person/i),
  151. suffix: 'txtValue',
  152. token: 'prtWorkSearchPersonContacted',
  153. },
  154. {
  155. regex: new RegExp(/email|website|url/i),
  156. suffix: 'txtValue',
  157. token: 'prtWebSiteURLEmailContact',
  158. },
  159. {
  160. regex: new RegExp(/phone|fax/i),
  161. suffix: 'txtValue',
  162. token: 'prtPhoneFaxNumber',
  163. },
  164. {
  165. regex: new RegExp(/address.+1|1.+address/i),
  166. suffix: 'txtValue',
  167. token: 'prtAddress1',
  168. },
  169. {
  170. regex: new RegExp(/address.+2|2.+address/i),
  171. suffix: 'txtValue',
  172. token: 'prtAddress2',
  173. },
  174. {
  175. regex: new RegExp(/city/i),
  176. suffix: 'txtValue',
  177. token: 'prtCity',
  178. },
  179. {
  180. regex: new RegExp(/state|province/i),
  181. suffix: 'txtValue',
  182. token: 'prtOtherState',
  183. },
  184. {
  185. regex: new RegExp(/postal|zip/i),
  186. suffix: 'txtValue',
  187. token: 'prtPostalCode',
  188. },
  189. {
  190. regex: new RegExp(/country/i),
  191. formatter: (value) =>
  192. ({
  193. Afghanistan: 329,
  194. Akrotiri: 377,
  195. Albania: 405,
  196. Algeria: 406,
  197. Andorra: 407,
  198. Angola: 450,
  199. Anguilla: 378,
  200. Antarctica: 341,
  201. 'Antigua and Barbuda': 283,
  202. Argentina: 360,
  203. Armenia: 408,
  204. Aruba: 478,
  205. 'Ashmore and Cartier Islands': 268,
  206. Australia: 361,
  207. Austria: 409,
  208. Azerbaijan: 342,
  209. 'Bahamas, The': 319,
  210. Bahrain: 410,
  211. Bangladesh: 343,
  212. Barbados: 379,
  213. 'Bassas da India': 297,
  214. Belarus: 411,
  215. Belgium: 412,
  216. Belize: 451,
  217. Benin: 479,
  218. Bermuda: 413,
  219. Bhutan: 452,
  220. Bolivia: 414,
  221. 'Bosnia and Herzegovina': 275,
  222. Botswana: 380,
  223. 'Bouvet Island': 311,
  224. Brazil: 453,
  225. 'British Indian Ocean Territory': 267,
  226. 'British Virgin Islands': 276,
  227. Brunei: 454,
  228. Bulgaria: 381,
  229. 'Burkina Faso': 320,
  230. Burma: 480,
  231. Burundi: 415,
  232. Cambodia: 382,
  233. Cameroon: 383,
  234. Canada: 448,
  235. 'Cape Verde': 344,
  236. 'Cayman Islands': 303,
  237. 'Central African Republic': 270,
  238. Chad: 505,
  239. Chile: 481,
  240. China: 482,
  241. 'Christmas Island': 291,
  242. 'Clipperton Island': 287,
  243. 'Cocos (Keeling) Islands': 273,
  244. Colombia: 384,
  245. Comoros: 416,
  246. 'Congo, Democratic Republic of the': 262,
  247. 'Congo, Republic of the': 277,
  248. 'Cook Islands': 321,
  249. 'Coral Sea Islands': 288,
  250. 'Costa Rica': 345,
  251. // eslint-disable-next-line quotes
  252. "Cote d'Ivoire": 312,
  253. Croatia: 417,
  254. Cuba: 506,
  255. Cyprus: 455,
  256. 'Czech Republic': 304,
  257. Denmark: 418,
  258. Dhekelia: 385,
  259. Djibouti: 386,
  260. Dominica: 387,
  261. 'Dominican Republic': 286,
  262. Ecuador: 419,
  263. Egypt: 483,
  264. 'El Salvador': 330,
  265. 'Equatorial Guinea': 289,
  266. Eritrea: 420,
  267. Estonia: 421,
  268. Ethiopia: 388,
  269. 'Europa Island': 313,
  270. 'Falkland Islands (Islas Malvinas)': 263,
  271. 'Faroe Islands': 314,
  272. Fiji: 507,
  273. Finland: 422,
  274. France: 456,
  275. 'French Guiana': 315,
  276. 'French Polynesia': 292,
  277. 'French Southern and Antarctic Lands': 261,
  278. Gabon: 484,
  279. 'Gambia, The': 331,
  280. 'Gaza Strip': 346,
  281. Georgia: 423,
  282. Germany: 424,
  283. Ghana: 485,
  284. Gibraltar: 362,
  285. 'Glorioso Islands': 293,
  286. Greece: 457,
  287. Greenland: 363,
  288. Grenada: 425,
  289. Guadeloupe: 347,
  290. Guam: 508,
  291. Guatemala: 364,
  292. Guernsey: 389,
  293. Guinea: 458,
  294. 'Guinea-Bissau': 316,
  295. Guyana: 459,
  296. Haiti: 486,
  297. 'Heard Island and McDonald Islands': 264,
  298. 'Holy See (Vatican City)': 274,
  299. Honduras: 390,
  300. 'Hong Kong': 365,
  301. Hungary: 426,
  302. Iceland: 427,
  303. India: 487,
  304. Indonesia: 366,
  305. Iran: 509,
  306. Iraq: 510,
  307. Ireland: 428,
  308. 'Isle of Man': 332,
  309. Israel: 460,
  310. Italy: 488,
  311. Jamaica: 429,
  312. 'Jan Mayen': 367,
  313. Japan: 489,
  314. Jersey: 461,
  315. Jordan: 462,
  316. 'Juan de Nova Island': 284,
  317. Kazakhstan: 348,
  318. Kenya: 490,
  319. Kiribati: 391,
  320. 'Korea, North': 322,
  321. 'Korea, South': 323,
  322. Kuwait: 463,
  323. Kyrgyzstan: 349,
  324. Laos: 511,
  325. Latvia: 464,
  326. Lebanon: 430,
  327. Lesotho: 431,
  328. Liberia: 432,
  329. Libya: 491,
  330. Liechtenstein: 317,
  331. Lithuania: 368,
  332. Luxembourg: 350,
  333. Macau: 492,
  334. Macedonia: 369,
  335. Madagascar: 351,
  336. Malawi: 465,
  337. Malaysia: 392,
  338. Maldives: 393,
  339. Mali: 512,
  340. Malta: 493,
  341. 'Marshall Islands': 294,
  342. Martinique: 352,
  343. Mauritania: 353,
  344. Mauritius: 370,
  345. Mayotte: 433,
  346. Mexico: 449,
  347. 'Micronesia, Federated States of': 266,
  348. Moldova: 434,
  349. Monaco: 466,
  350. Mongolia: 394,
  351. Montserrat: 354,
  352. Morocco: 435,
  353. Mozambique: 355,
  354. Namibia: 436,
  355. Nauru: 494,
  356. 'Navassa Island': 305,
  357. Nepal: 495,
  358. Netherlands: 333,
  359. 'Netherlands Antilles': 281,
  360. 'New Caledonia': 318,
  361. 'New Zealand': 334,
  362. Nicaragua: 371,
  363. Niger: 496,
  364. Nigeria: 437,
  365. Niue: 513,
  366. 'Norfolk Island': 306,
  367. 'Northern Mariana Islands': 271,
  368. Norway: 467,
  369. Oman: 514,
  370. Pakistan: 395,
  371. Palau: 497,
  372. Panama: 468,
  373. 'Papua New Guinea': 295,
  374. 'Paracel Islands': 298,
  375. Paraguay: 396,
  376. Peru: 515,
  377. Philippines: 335,
  378. 'Pitcairn Islands': 296,
  379. Poland: 469,
  380. Portugal: 397,
  381. 'Puerto Rico': 336,
  382. Qatar: 498,
  383. Reunion: 438,
  384. Romania: 439,
  385. Russia: 470,
  386. Rwanda: 471,
  387. 'S Georgia and S Sandwich Islands': 260,
  388. 'Saint Helena': 324,
  389. 'Saint Kitts and Nevis': 278,
  390. 'Saint Lucia': 337,
  391. 'Saint Pierre and Miquelon': 269,
  392. 'Saint Vincent and the Grenadines': 265,
  393. Samoa: 499,
  394. 'San Marino': 356,
  395. 'Sao Tome and Principe': 279,
  396. 'Saudi Arabia': 325,
  397. Senegal: 440,
  398. 'Serbia and Montenegro': 280,
  399. Seychelles: 357,
  400. 'Sierra Leone': 326,
  401. Singapore: 372,
  402. Slovakia: 398,
  403. Slovenia: 399,
  404. 'Solomon Islands': 299,
  405. Somalia: 441,
  406. 'South Africa': 327,
  407. Spain: 500,
  408. 'Spratly Islands': 300,
  409. 'Sri Lanka': 373,
  410. Sudan: 501,
  411. Suriname: 400,
  412. Svalbard: 401,
  413. Swaziland: 374,
  414. Sweden: 472,
  415. Switzerland: 338,
  416. Syria: 502,
  417. Taiwan: 473,
  418. Tajikistan: 358,
  419. Tanzania: 402,
  420. Thailand: 403,
  421. 'Timor-Leste': 339,
  422. Togo: 516,
  423. Tokelau: 442,
  424. Tonga: 503,
  425. 'Trinidad and Tobago': 285,
  426. 'Tromelin Island': 301,
  427. Tunisia: 443,
  428. Turkey: 474,
  429. Turkmenistan: 328,
  430. 'Turks and Caicos Islands': 272,
  431. Tuvalu: 475,
  432. Uganda: 476,
  433. Ukraine: 444,
  434. 'United Arab Emirates': 282,
  435. 'United Kingdom': 307,
  436. 'United States': 310,
  437. Uruguay: 445,
  438. Uzbekistan: 359,
  439. Vanuatu: 446,
  440. Venezuela: 375,
  441. Vietnam: 447,
  442. 'Virgin Islands': 308,
  443. 'Wake Island': 340,
  444. 'Wallis and Futuna': 290,
  445. 'West Bank': 376,
  446. 'Western Sahara': 309,
  447. Yemen: 504,
  448. Zambia: 477,
  449. Zimbabwe: 404,
  450. }[String(value).trim()]),
  451. suffix: 'ddlValue',
  452. token: 'prtCountry',
  453. },
  454. ];
  455.  
  456. function inputByHeaders(headersArray) {
  457. const headers = headersArray.map((header) => {
  458. const input = inputs.find(({regex}) => regex.test(header));
  459.  
  460. if (!input) {
  461. return null;
  462. }
  463.  
  464. return input;
  465. }, []);
  466.  
  467. return headers;
  468. }
  469.  
  470. const wrapWeekId = (token) =>
  471. wrapInputId(
  472. token,
  473. '',
  474. [
  475. 'contentMain',
  476. 'contentMain',
  477. 'ucFormCCA4581RegularDUACertificationWeeks',
  478. ],
  479. ''
  480. );
  481.  
  482. function load(data) {
  483. const {headers, rows} = data;
  484. headers.forEach((header) => {
  485. if (!header) {
  486. return;
  487. }
  488.  
  489. const {token, suffix} = header;
  490.  
  491. const element = document.querySelector(wrapInputId(token, suffix));
  492.  
  493. if (!element) {
  494. throw new Error(`Could not find element for ${token}`);
  495. }
  496.  
  497. header.element = element;
  498. });
  499.  
  500. const row = rows[0];
  501. headers.forEach((header, index) => {
  502. if (!header) {
  503. return;
  504. }
  505.  
  506. const {element, formatter} = header;
  507.  
  508. try {
  509. const value = formatter ? formatter(row[index]) : row[index];
  510.  
  511. fill(element, value);
  512. } catch (error) {
  513. if (error instanceof Error && error.name === 'RangeError') {
  514. console.warn(
  515. `Error filling ${getLabel(element)
  516. .toString()
  517. .trim()} with ${row[index]}`
  518. );
  519. }
  520. throw error;
  521. }
  522. });
  523.  
  524. const additionalButton = document.querySelector(
  525. wrapInputId('prtISAdditionalWorkSerach', ['rblValue', '0'])
  526. );
  527.  
  528. const nextButton = document.querySelector(
  529. wrapInputId('btnNext', '', ['contentMain', 'contentMain'], '')
  530. );
  531.  
  532. additionalButton.click();
  533.  
  534. nextButton.click();
  535. }
  536.  
  537. const weekNumber = document.querySelector(wrapWeekId('lblWeekNumber'));
  538. const weekEnd = document.querySelector(wrapWeekId('lblWeekEndDate'));
  539. const weekHeader = weekEnd.parentElement;
  540. weekHeader.style.display = 'flex';
  541. weekHeader.style.alignItems = 'center';
  542. weekNumber.style.padding = '0 0.5rem';
  543.  
  544. const csvUploadInput = document.createElement('input');
  545. csvUploadInput.style.display = 'none';
  546.  
  547. const csvUploadIcon = document.createElement('span');
  548. csvUploadIcon.classList.add('ca-gov-icon-file-csv');
  549.  
  550. const csvUploadText = document.createElement('span');
  551. csvUploadText.innerText = 'Upload CSV';
  552. csvUploadText.style.fontSize = '1rem';
  553. csvUploadText.style.userSelect = 'none';
  554.  
  555. csvUploadInput.type = 'file';
  556. csvUploadInput.accept = '.csv';
  557. csvUploadInput.addEventListener('change', (event) => {
  558. const file = event.target.files[0];
  559.  
  560. const reader = new FileReader();
  561.  
  562. reader.onload = (event) => {
  563. const csv = event.target.result;
  564. const csvArray = CSVToArray(csv);
  565.  
  566. const [headers, ...rows] = csvArray;
  567.  
  568. if (!rows.length) {
  569. return;
  570. }
  571.  
  572. const inputHeaders = inputByHeaders(headers);
  573.  
  574. window.localStorage.setItem('csvArray', JSON.stringify(csvArray));
  575.  
  576. const data = {
  577. headers: inputHeaders,
  578. rows,
  579. };
  580.  
  581. load(data);
  582. };
  583.  
  584. reader.readAsText(file);
  585. });
  586.  
  587. const csvUploadLabel = document.createElement('label');
  588. csvUploadLabel.classList.add('nav-item');
  589. csvUploadLabel.style.marginLeft = '5px';
  590. csvUploadLabel.style.display = 'flex';
  591. csvUploadLabel.style.alignItems = 'center';
  592. csvUploadLabel.appendChild(csvUploadInput);
  593. csvUploadLabel.appendChild(csvUploadIcon);
  594. csvUploadLabel.appendChild(csvUploadText);
  595.  
  596. weekHeader.appendChild(csvUploadLabel);
  597.  
  598. const csvArray = JSON.parse(window.localStorage.getItem('csvArray'));
  599.  
  600. if (csvArray) {
  601. const [headers, ...rows] = csvArray;
  602. rows.shift();
  603.  
  604. if (!rows.length) {
  605. window.localStorage.removeItem('csvArray');
  606. }
  607.  
  608. if (rows.length) {
  609. window.localStorage.setItem(
  610. 'csvArray',
  611. JSON.stringify([headers, ...rows])
  612. );
  613.  
  614. const inputHeaders = inputByHeaders(headers);
  615.  
  616. const data = {
  617. headers: inputHeaders,
  618. rows,
  619. };
  620.  
  621. load(data);
  622. }
  623. }