ao-helper

arthur online helper

  1. // ==UserScript==
  2. // @name ao-helper
  3. // @description arthur online helper
  4. // @version 1.0.9
  5. // @author yuze
  6. // @namespace yuze
  7. // @match https://system.arthuronline.co.uk/*
  8. // @connect arthuronline.co.uk
  9. // @connect ea-api.yuze.now.sh
  10. // @grant GM.getValue
  11. // @grant GM.setValue
  12. // @grant GM.xmlHttpRequest
  13. // @require https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js
  14. // ==/UserScript==
  15.  
  16. /* eslint-env jquery */
  17. /* globals moment, GM */
  18.  
  19. let id;
  20. let icons = {}
  21. let storage = {}
  22. let targets = {}
  23.  
  24. let local = false
  25. let letter_endpoint
  26.  
  27. init()
  28. function init() {
  29. $(window).on('load focus mousedown', checkLocation)
  30. $(window).on('load', loadActions)
  31. $(window).on('mousedown', mousedownActions)
  32. $(window).on('hashchange', checkTab)
  33.  
  34. appendCSS()
  35. loadIcons()
  36.  
  37. id = $('.text-logo span').text().toLowerCase()
  38.  
  39. if (local) {
  40. letter_endpoint = 'https://localhost:3000/api/gen-letter'
  41. console.log('using localhost endpoint')
  42. } else {
  43. letter_endpoint = 'https://ea-api.yuze.now.sh/api/gen-letter'
  44. }
  45.  
  46. targets = {
  47. addTransaction: {
  48. btn: '.box-header .actions ul > :nth-child(3)',
  49. date: 'input#TransactionDate',
  50. desc: 'input#TransactionDescription',
  51. amount: 'input#TransactionAmount',
  52. },
  53. documents: {
  54. checkboxes: 'table.attachments input[type="checkbox"]'
  55. }
  56. }
  57. }
  58.  
  59. function loadActions() {
  60. //quickAccess()
  61. quickNotes()
  62. }
  63.  
  64. function mousedownActions(e) {
  65. if ( event.which == 2 || event.which == 3 ) return;
  66.  
  67. copyOnClick(e)
  68. addTransactionBtn(e)
  69. }
  70.  
  71. async function wait(ms) {
  72. return new Promise(resolve => {
  73. setTimeout( () => { resolve() }, ms);
  74. });
  75. }
  76.  
  77. async function waitForExistance(elem, resolve) {
  78.  
  79. if ( $(elem).length ) {
  80. resolve()
  81. }
  82.  
  83. let interval;
  84. if ( !$(elem).length ) {
  85. interval = setInterval( () => checkExistance(), 256)
  86. }
  87. function checkExistance() {
  88. if ( $(elem).length ) {
  89. clearInterval(interval)
  90. resolve()
  91. }
  92. }
  93. }
  94.  
  95. async function waitForIframeExistance(iframe, elem, resolve) {
  96. if ( $(iframe).contents().find(elem).length ) {
  97. resolve()
  98. }
  99. let interval;
  100. if ( !$(iframe).contents().find(elem).length ) {
  101. interval = setInterval( () => checkExistance(), 128)
  102. }
  103. function checkExistance() {
  104. if ( $(iframe).contents().find(elem).length ) {
  105. clearInterval(interval)
  106. resolve()
  107. }
  108. }
  109. }
  110.  
  111. async function checkLocation() {
  112. $(window).trigger('hashchange')
  113. await wait(256)
  114. if( /tenancies\/view\/\d{6}/.test(window.location.href) ) {
  115. viewTenancies()
  116. }
  117. if( /\/\w+\/(page|filter|index)/.test(window.location.href) ) {
  118. viewSearch()
  119. }
  120. }
  121.  
  122. async function checkTab() {
  123. await wait(64)
  124. if( !location.hash.includes('tab-transactions') ) {
  125. tabTransactions(true)
  126. }
  127. if( !location.hash.includes('tab-recurrings') ) {
  128. tabRecurrings(true)
  129. }
  130. if( !location.hash.includes('tab-attachments') ) {
  131. tabAttachments(true)
  132. }
  133.  
  134. if( location.hash.includes('tab-transactions') ) {
  135. tabTransactions(false)
  136. }
  137. if( location.hash.includes('tab-recurrings') ) {
  138. tabRecurrings(false)
  139. }
  140. if( location.hash.includes('tab-attachments') ) {
  141. tabAttachments(false)
  142. }
  143. }
  144.  
  145. function viewTenancies() {
  146. new Promise( function(resolve) {
  147. waitForExistance('.identifier', resolve)
  148. }).then( () => {
  149. appendTenantToolbox()
  150. appendReadableDateRange()
  151. } )
  152. }
  153.  
  154. function viewSearch() {
  155. $('#_q').off('paste', querySeperator)
  156. $('#_q').on('paste', querySeperator)
  157. }
  158.  
  159. function appendTenantToolbox() {
  160.  
  161. if ( $('#tenantToolbox').length ) return;
  162.  
  163. const option = {
  164. bfc: ` <div class="option" id="tenantToolbox_bfc">
  165. <div class="icon red">${icons.percent}</div><span class="red">Breaking Fee Calculator</span>
  166. </div>`,
  167. letter: `<div class="option" id="tenantToolbox_letter">
  168. <div class="icon red">${icons.letter}</div><span class="red">Overdue Rent Letter</span>
  169. </div>`
  170. }
  171.  
  172. const html = ` <div id="tenantToolbox" class="element-container">
  173. <div class="element-header">
  174. <div class="title">${id[0].toUpperCase() + id.slice(1)} tools</div>
  175. <div style="height: 4px"></div>
  176. ${option.bfc}
  177. ${option.letter}
  178. </div>
  179. </div>`
  180.  
  181. $('.left-side-detail').append(html)
  182.  
  183. $('#tenantToolbox').on('click', tenantToolboxActions)
  184. }
  185.  
  186. function tenantToolboxActions(e) {
  187. if( e.target.id == 'tenantToolbox' ) return;
  188. if( e.target.closest('.option').id == 'tenantToolbox_bfc') {
  189. prepareModal('bfc')
  190. return;
  191. }
  192. if( e.target.id == 'tenantToolbox' ) return;
  193. if( e.target.closest('.option').id == 'tenantToolbox_letter') {
  194. genLetter()
  195. return;
  196. }
  197. }
  198.  
  199. async function genLetter() {
  200.  
  201. let savedLoc = window.location.href;
  202. let property = function() {
  203. let arr = $('.identifier-detail .sub-title a:nth-of-type(1)').text().split(',')
  204. arr.splice(1, 1)
  205. return arr.join(',')
  206. }()
  207.  
  208. let ref = $('.identifier-detail .title').text().match(/TE\d{4,5}/)[0]
  209. let total = $('.financial-transaction .overdue .number').text()
  210.  
  211. let arrears;
  212. let dueDate;
  213.  
  214. getArrears()
  215. function getArrears() {
  216. $('.nav.nav-tabs [href^="#tab-transactions"]')[0].click()
  217.  
  218. new Promise( function(resolve) {
  219. waitForExistance('.transactions tbody', resolve)
  220. }).then(() => {
  221.  
  222. // arrears data
  223. $('#genOverdueBtn')[0].click()
  224. arrears = $('#genOverdueText')[0].value.split('\n\n')[0]
  225. $('#genOverdueText').css('display', 'none')
  226.  
  227. // extract due date
  228. dueDate = arrears.split('\n')
  229.  
  230. for (let i = 0; i <= dueDate.length; i++) {
  231. if ( !dueDate[i] && i === 0 ) {
  232. break;
  233. } else if ( i == dueDate.length - 1 ) {
  234.  
  235. try {
  236. dueDate = dueDate[0].match(/\((\d.+)-/)[1].trim()
  237. break;
  238. } catch(err) {
  239. dueDate = dueDate[0].match(/\((\d.+)\)/)[1].trim()
  240. break;
  241. }
  242. } else if ( dueDate[i].includes('Outstanding') ) {
  243. dueDate = dueDate[i].match(/\((.+)-/)[1].trim()
  244. break;
  245. }
  246.  
  247. }
  248. getNames()
  249. })
  250. }
  251.  
  252. let names = []
  253. let namesFirst = []
  254. let fullName;
  255. let firstName;
  256.  
  257. function getNames() {
  258. $('.nav.nav-tabs [href^="#tab-summary"]')[0].click()
  259.  
  260. new Promise( function(resolve) {
  261. waitForExistance('.title .name h3', resolve)
  262. }).then(() => {
  263. for(let i = 0; i < $('.title .name h3').length; i++) {
  264. names.push( $('.title .name h3')[i].textContent )
  265. }
  266.  
  267. for (let i = 0; i < names.length; i++) {
  268. fullName = names.join(', ')
  269. }
  270. for (let i = 0; i < names.length; i++) {
  271. namesFirst.push( names[i].split(' ')[0] )
  272. }
  273.  
  274. if( names.length > 2) {
  275. firstName = namesFirst.join(', ').replace(/(,)(?!.+\1)/, ' and')
  276. } else {
  277. firstName = namesFirst.join(' and ')
  278. }
  279. returnToSavedLocation()
  280. sendRequest()
  281. })
  282. }
  283.  
  284. function sendRequest() {
  285.  
  286. GM.xmlHttpRequest({
  287. method: "POST",
  288. url: letter_endpoint,
  289. data: `for=${id}&name=${names[0]}&full_name=${fullName}&first_name=${firstName}&property=${property}&ref=${ref}&arrears=${arrears}&due_date=${dueDate}&total=${total}`,
  290. headers: {
  291. "Content-Type": "application/x-www-form-urlencoded"
  292. },
  293. onload: function(res) {
  294. console.log(res.response)
  295. window.open(`${letter_endpoint}/${names[0]}/${property}/`)
  296. }
  297. })
  298.  
  299. }
  300.  
  301. function returnToSavedLocation() {
  302. if( /tenancies\/view\/\d{6}\/ident:Datatable.{5}$/.test(savedLoc) ){
  303. setTimeout( async () => {
  304. $(`.nav.nav-tabs [href^="#tab-summary"]`)[0].click()
  305. await wait(512)
  306. checkLocation()
  307. }, 1)
  308. } else if ( /tenancies\/view\/\d{6}\/ident:Datatable.{5}#tab-.+-/.test(savedLoc) ) {
  309. let match = savedLoc.match(/tab-.+(?=-)/)[0]
  310. setTimeout( async () => {
  311. $(`.nav.nav-tabs [href^="#${match}"]`)[0].click()
  312. await wait(512)
  313. checkLocation()
  314. }, 1)
  315. }
  316. }
  317. }
  318.  
  319. async function prepareModal(mode) {
  320.  
  321. let template = {}
  322.  
  323. if ( mode == 'bfc') {
  324. prepareDataBfc('bfc')
  325. }
  326.  
  327. async function prepareDataBfc(mode) {
  328. let data = {}
  329.  
  330. data.today = currentDate()
  331. data.endOfTenancy = storage.tenancyEndDate
  332. data.monthly = ''
  333.  
  334. if ( $('.summary-data-container div:nth-child(2) p').length ) {
  335. data.monthly = $('.summary-data-container div:nth-child(2) p').text().trim().replace(' monthly', '')
  336. makeTemplateBfc(data, mode)
  337. } else {
  338. $('.nav.nav-tabs [href^="#tab-summary"]')[0].click()
  339. new Promise( function(resolve) {
  340. waitForExistance('.summary-data-container div:nth-child(2) p', resolve)
  341. }).then( () => {
  342. data.monthly = $('.summary-data-container div:nth-child(2) p').text().trim().replace(' monthly', '')
  343. makeTemplateBfc(data, mode)
  344. } )
  345. }
  346. }
  347.  
  348. function makeTemplateBfc(data, mode) {
  349.  
  350. template.postLaunchActions = true
  351. template.title = 'Breaking Fee Calculator'
  352. template.close = 'Understood'
  353. template.color = 'red'
  354. template.body = `One month's rent is&ensp;<input class="monthly red" type="text" value="£0.00" style="width: 7.5ch"><br>
  355. <hr>
  356. <input class="from red" type="date" value="2020-01-01" style="width: 12.25ch">&ensp;to&ensp;<input class="to red" type="date" value="2020-01-01" style="width: 12.25ch">&ensp;is <b><span class="days">0</span> days</b><br>
  357. That's <span class="daysBetweenReadable"><b>0 months, 0 weeks</b> and <b>0 days</b></span>
  358. <hr>
  359. Weekly rent is <b>£<span class="weekly curr">0.00</span></b> which is <b>£<span class="daily curr">0.00</span></b> per day<br>
  360. <b class="start">£<span class="daily curr">0.00</span></b> multiplied by <b><span class="days">0</span> days</b> is <b>£<span class="dailyByDays curr">0.00</span></b>
  361. <hr>
  362. <input type="text" value="0%" style="width: 2.75ch" class="percent red"">&ensp;of <b>£<span class="dailyByDays curr">0.00</span></b> is <b>£<span class="percentOfRemaining curr">0.00</span></b>`
  363. template.footer = `<span style="color: #d64a65">Total to be paid: <b class="totalStyle">£<span class="total">0.00</span></b></span>`
  364. createModal(template, data, mode)
  365. }
  366. }
  367.  
  368. function createModal(template, data, mode) {
  369.  
  370. if ( $('#fModal').length ) return;
  371.  
  372. let { title, body, footer, close, color, postLaunchActions } = template;
  373.  
  374. const html = ` <div id="fModal" class="${color}">
  375. <div class="header ${color}">${title}</div>
  376. <div class="body">${body}</div>
  377. <div class="footer">
  378. <div class="left">${footer}</div>
  379. <div class="right"><div class="btn ${color}" id="fModalClose">${close}</div></div>
  380. </div>
  381. </div>`
  382.  
  383. $('body').append(html)
  384. $('#fModal').hide().fadeIn(314)
  385.  
  386. //CLOSE
  387. $('#fModalClose').on('click', removeModal)
  388.  
  389. //MOVE
  390. $('#fModal .header').on('mousedown', moveModal)
  391.  
  392. function moveModal(e) {
  393. let x = e.clientX - ( $('#fModal .header')[0].getBoundingClientRect().left + ( $('#fModal .header')[0].getBoundingClientRect().width / 2 ) )
  394. let y = e.clientY - $('#fModal .header')[0].getBoundingClientRect().top
  395.  
  396. $('body').on('mousemove', beginDrag)
  397. function beginDrag(e) {
  398. $('#fModal').css('left', e.clientX - x + "px")
  399. $('#fModal').css('top', + e.clientY - y + "px")
  400. }
  401.  
  402. $('#fModal .header').on('mouseup', endDrag)
  403. function endDrag(){
  404. $('body').off('mousemove', beginDrag)
  405. }
  406. }
  407.  
  408. if ( postLaunchActions ) {
  409. modalPostLaunch(data, mode)
  410. }
  411. }
  412.  
  413. function modalPostLaunch(data, mode) {
  414. if ( mode == 'bfc') {
  415. postLaunchBfc(data)
  416. }
  417.  
  418. function postLaunchBfc(data) {
  419.  
  420. let { monthly, today, endOfTenancy } = data;
  421.  
  422. initialise()
  423. function initialise() {
  424. $('#fModal .monthly')[0].value = monthly
  425. $('#fModal .from')[0].value = today
  426. $('#fModal .to')[0].value = endOfTenancy
  427. $('#fModal .percent')[0].value = 7
  428.  
  429. console.log(endOfTenancy)
  430. }
  431. calc()
  432. function calc() {
  433.  
  434. let monthly = $('#fModal .monthly')[0].value
  435. let percent = $('#fModal .percent')[0].value
  436.  
  437. clearFormatting()
  438. function clearFormatting() {
  439. // monthly
  440. if ( $('#fModal .monthly')[0].value.includes('£') ) {
  441. monthly = $('#fModal .monthly')[0].value.slice(1)
  442. }
  443. if ( monthly.includes(',') ) {
  444. monthly = monthly.replace(',', '')
  445. }
  446. // percent
  447. if ( $('#fModal .percent')[0].value.includes('%') ) {
  448. percent = $('#fModal .percent')[0].value.replace('%', '')
  449. }
  450. }
  451.  
  452. let daysBetweenArray = differenceBetweenDates($('#fModal .from')[0].value, $('#fModal .to')[0].value)
  453. for(let i = 0; i < $('#fModal .days').length; i++ ) {
  454. if ( $('#fModal .days')[i].innerHTML == 'NaN' ) {
  455. $('#fModal .days')[i].innerHTML = '0'
  456. continue;
  457. }
  458. $('#fModal .days')[i].innerHTML = daysBetweenArray[0]
  459. }
  460. $('#fModal .daysBetweenReadable')[0].innerHTML = daysBetweenArray[1]
  461.  
  462. $('#fModal .weekly')[0].innerHTML = function() {
  463.  
  464. let weekly = String( (parseFloat(monthly) * 12 / 52).toFixed(3) ).slice(0, -1) ;
  465.  
  466. if( weekly.match(/\.\d(\d)/)[1] == '9' ) {
  467. weekly = +weekly + 0.01
  468. } else if ( weekly.match(/\.\d(\d)/)[1] == '1' ) {
  469. weekly = +weekly - 0.01
  470. } else {
  471. weekly = +weekly
  472. }
  473.  
  474. return String( weekly.toFixed(3) ).slice(0,-1)
  475. }()
  476.  
  477. for(let i = 0; i < $('#fModal .daily').length; i++ ) {
  478. $('#fModal .daily')[i].innerHTML = +String( +$('#fModal .weekly')[0].innerHTML / 7 ).match(/\d+(\.\d{2})?/)[0]
  479. }
  480.  
  481.  
  482. for(let i = 0; i < $('#fModal .dailyByDays').length; i++ ) {
  483. $('#fModal .dailyByDays')[i].innerHTML = +String(+$('#fModal .daily')[0].innerHTML * +$('#fModal .days')[0].innerHTML).match(/\d+(\.\d{2})?/)[0]
  484. }
  485.  
  486. $('#fModal .percentOfRemaining')[0].innerHTML = +String(+$('#fModal .dailyByDays')[0].innerHTML * ( percent / 100 ) ).match(/\d+(\.\d{2})?/)[0]
  487.  
  488. let total = String(+monthly + +$('#fModal .percentOfRemaining')[0].innerHTML)
  489. if ( !total.includes('.') ) {
  490. total = String(+monthly + +$('#fModal .percentOfRemaining')[0].innerHTML) + '.00'
  491. }
  492. if ( total.match(/\.\d{1}(?!\d)/) ) {
  493. total = String(+monthly + +$('#fModal .percentOfRemaining')[0].innerHTML) + '0'
  494. }
  495. total = total.match(/[0-9]+[.,]\d{2}/)[0]
  496. $('#fModal .total')[0].innerHTML = total.replace(/(\d{1})(\d{3}.\d{1,2})/, '$1,$2')
  497. }
  498.  
  499. format()
  500. function format() {
  501. // currentcy input
  502. if ( !$('#fModal .monthly')[0].value.includes('£') ) {
  503. $('#fModal .monthly')[0].value = '£' + $('#fModal .monthly')[0].value
  504. }
  505. if ( !$('#fModal .monthly')[0].value.includes('.') ) {
  506. $('#fModal .monthly')[0].value = $('#fModal .monthly')[0].value + '.00'
  507. }
  508. if ( $('#fModal .monthly')[0].value.match(/(£\d{1})(\d{3}.\d{1,2})/) ) {
  509. $('#fModal .monthly')[0].value = $('#fModal .monthly')[0].value.replace(/(£\d{1})(\d{3}.\d{1,2})/, '$1,$2')
  510. }
  511. if( $(`#fModal .monthly`)[0].value.match(/\.\d{1}(?!\d)/g) ){
  512. $(`#fModal .monthly`)[0].value = $(`#fModal .monthly`)[0].value + 0
  513. }
  514. // query currencies
  515. for (let i = 0; i < $('#fModal .curr').length; i++) {
  516. if ( !$(`#fModal .curr`)[i].innerHTML.includes('.') ) {
  517. $(`#fModal .curr`)[i].innerHTML = $(`#fModal .curr`)[i].innerHTML + '.00'
  518. }
  519. if ( $(`#fModal .curr`)[i].innerHTML.match(/(\d{1})(\d{3}.\d{1,2})/) ) {
  520. $(`#fModal .curr`)[i].innerHTML = $(`#fModal .curr`)[i].innerHTML.replace(/(\d{1})(\d{3}.\d{1,2})/, '$1,$2')
  521. }
  522. if( $(`#fModal .curr`)[i].innerHTML.match(/\.\d{1}(?!\d)/g) ){
  523. $(`#fModal .curr`)[i].innerHTML = $(`#fModal .curr`)[i].innerHTML + 0
  524. }
  525. }
  526. // percent
  527. if ( !$('#fModal .percent')[0].value.includes('%') ) {
  528. $('#fModal .percent')[0].value = $('#fModal .percent')[0].value + '%'
  529. }
  530. }
  531.  
  532. resize()
  533. function resize() {
  534. // currency
  535. if ( $('#fModal .monthly')[0].value.includes(',') ) {
  536. $('#fModal .monthly').css('width', $('#fModal .monthly')[0].value.length - 0.25 + 'ch')
  537. } else {
  538. $('#fModal .monthly').css('width', $('#fModal .monthly')[0].value.length + 'ch')
  539. }
  540. // percent
  541. if ( $('#fModal .percent')[0].value.includes('.') ) {
  542. $('#fModal .percent').css('width', $('#fModal .percent')[0].value.length + 0.25 + 'ch')
  543. } else {
  544. $('#fModal .percent').css('width', $('#fModal .percent')[0].value.length + 0.75 + 'ch')
  545. }
  546. }
  547.  
  548. listeners()
  549. function listeners() {
  550. $('#fModal .monthly, #fModal .from, #fModal .to').on('input', function() {
  551. calc()
  552. resize()
  553. })
  554. $('#fModal .percent').on('input', function() {
  555. calc()
  556. resize()
  557. })
  558. $('#fModal .monthly, #fModal .percent, #fModal .from, #fModal .to').on('change', function() {
  559. calc()
  560. format()
  561. resize()
  562. })
  563. $('#fModal .monthly, #fModal .percent').on('focus click', function(e) {
  564. e.target.select()
  565. })
  566. let timer;
  567. $('#fModal .monthly, #fModal .percent').on('keyup', function(e) {
  568. clearInterval(timer)
  569. timer = setTimeout( () => {
  570. e.target.blur()
  571. }, 1218)
  572. })
  573. }
  574. }
  575.  
  576. }
  577.  
  578. function removeModal() {
  579. $('#fModal').fadeOut(314, function () {
  580. $('#fModal').remove()
  581. })
  582. }
  583.  
  584. function tabTransactions(remove) {
  585. if (remove) {
  586. $('#genOverdueBtn').remove()
  587. $('#genOverdueContainer').remove()
  588. $('.genCopyIcon').remove()
  589. return;
  590. }
  591.  
  592. overdueGenReport()
  593. function overdueGenReport() {
  594. appendOverdueBtn()
  595. function appendOverdueBtn() {
  596. if ( $('#genOverdueBtn').length ) return;
  597. const html = `<button id="genOverdueBtn" class="btn btnPad btnSpecial">Generate Overdue Report</button>`
  598. $('body').append(html)
  599. $('#genOverdueBtn').on('click', appendOverdueDisplay)
  600. }
  601. function appendOverdueDisplay() {
  602. $('#genOverdueBtn').off('click', appendOverdueDisplay)
  603. const html = `<textarea id="genOverdueText" class="input textarea textareaSpecial"></textarea>${icons.copy}`
  604. $('body').append(html)
  605. $('#genOverdueText').hide().fadeIn(618)
  606. $('.genCopyIcon').hide().fadeIn(618)
  607. $('#genOverdueText').on('focusout', removeOverdueReport)
  608. $('#genOverdueText').focus()
  609. $('.genCopyIcon').on('mousedown', copyOverdueReport)
  610. getOverdueData()
  611. }
  612. function getOverdueData() {
  613. let overdueArray = [];
  614. // OLD FUCKED UP AO-HELPER LOGIC START
  615. let getTransactionLength = $('.transactions tbody').children().length
  616. for (let i = 0; i < getTransactionLength; i++) {
  617. if ( $('.transactions tbody').children().eq(i).children().eq(1).text().includes('Overdue') ) {
  618. let getOutstanding = $('.transactions tbody').children().eq(i).children().eq(9).text().trim()
  619. let getDesc = $('.transactions tbody').children().eq(i).children().eq(3).text().split('Tenancy:')[0].trim()
  620. let getDate = $('.transactions tbody').children().eq(i).children().eq(1).text().split('Due')[0]
  621. if ( getDesc.includes('Default tenancy rent charge transaction type') ) {
  622. getDesc = 'Outstanding Rent'
  623. getDate = $('.transactions tbody').children().eq(i).children().eq(3).text().split('Tenancy:')[0].trim().match(/\d{1,2}.{2} \w{3,4} \d{4} - \d{1,2}.{2} \w{3,4} \d{4}/)
  624. } else {
  625. getDesc = $('.transactions tbody').children().eq(i).children().eq(3).text().split('Tenancy:')[0].trim()
  626. }
  627. overdueArray.push([getOutstanding, getDesc, getDate])
  628. }
  629. }
  630. overdueArray.forEach( (i) => appendToGenReportTextbox(`${i[0]} ${i[1]} (${i[2]})`, false) )
  631. let formattedOverdueValues = []
  632. for (let i = 0; i < overdueArray.length; i++) {
  633. formattedOverdueValues[i] = +overdueArray[i][0].replace(',', '').slice(1)
  634. }
  635. let sum = '£' + String(formattedOverdueValues.reduce( (a, b) => a + b, 0))
  636. appendToGenReportTextbox('Total to be paid: ' + `${sum.replace(/£(\d{1,4}$)/, '£$1.00').replace(/£(\d{1})(\d{3})/, '£$1,$2')}`, true)
  637. // OLD FUCKED UP AO-HELPER LOGIC START
  638. resizeOverdueReport()
  639. }
  640. function appendToGenReportTextbox(data, end) {
  641. if (!end) {
  642. $('#genOverdueText').val( $('#genOverdueText').val() + data + '\n' );
  643. } else {
  644. $('#genOverdueText').val( $('#genOverdueText').val() + '\n' + data );
  645. }
  646. }
  647. function resizeOverdueReport() {
  648. $('#genOverdueText').width( $('#genOverdueText').prop('scrollWidth') + "px" )
  649. $('#genOverdueText').height( $('#genOverdueText').prop('scrollHeight') - 10 + "px" )
  650. $('.genCopyIcon').css('left', $('#genOverdueText')[0].getBoundingClientRect().right - 52 + "px")
  651. }
  652. function copyOverdueReport() {
  653. copyToClipboard($('#genOverdueText').val())
  654. copyToast('report')
  655. }
  656. function removeOverdueReport() {
  657. $('#genOverdueText').fadeOut(314, function() {
  658. $('#genOverdueText').remove()
  659. $('#genOverdueBtn').on('click', appendOverdueDisplay)
  660. })
  661. $('.genCopyIcon').fadeOut(314, function() {
  662. $('.genCopyIcon').remove()
  663. })
  664. }
  665. }
  666. }
  667.  
  668. function tabRecurrings(remove) {
  669.  
  670. if (remove) {
  671. $('#genNextMonthBtn').remove()
  672. return;
  673. }
  674.  
  675. // genNextMonth()
  676. function genNextMonth() {
  677. appendGenNextMonthBtn()
  678. function appendGenNextMonthBtn() {
  679. const html = `<button id="genNextMonthBtn" class="btn btnPad">Generate Next Month Invoice?</button>`
  680. new Promise( function(resolve) {
  681. waitForExistance('.recurrings .box-header .actions', resolve)
  682. }).then( () => {
  683. if ( $('#genNextMonthBtn').length ) return;
  684. $('.recurrings .box-header .actions').prepend(html)
  685. $('#genNextMonthBtn').on('click', automation)
  686. } )
  687. }
  688.  
  689. async function automation() {
  690. disableAccess('<b style="font-size: 1.5em;">Please wait 📅</b><br>Generating next month\'s invoice...')
  691. $('.recurrings .datatable-subactions ul > li:nth-child(3) a').click()
  692. new Promise( function(resolve) {
  693. waitForIframeExistance('#dialog iframe', 'input#RecurringDaysInAdvance', resolve)
  694. }).then( () => {
  695. let input = $('#dialog iframe').contents().find('input#RecurringDaysInAdvance')
  696. input.val(31)
  697. $('#dialog iframe').contents().find('input.submit-btn')[0].click()
  698. })
  699. await wait(2500)
  700.  
  701. new Promise ( function(resolve) {
  702. $('.recurrings .datatable-subactions ul > li:nth-child(3) a').click()
  703. waitForIframeExistance('#dialog iframe', 'input#RecurringDaysInAdvance', resolve)
  704. }).then( () => {
  705. let input = $('#dialog iframe').contents().find('input#RecurringDaysInAdvance')
  706. input.val(7)
  707. $('#dialog iframe').contents().find('input.submit-btn')[0].click()
  708. disableAccess('', true)
  709. })
  710. }
  711.  
  712.  
  713. }
  714. }
  715.  
  716. function tabAttachments(remove) {
  717. if (remove) {
  718. return;
  719. }
  720. }
  721.  
  722. function appendReadableDateRange() {
  723. if ( $('.readableDateRange').length ) return;
  724.  
  725. let getDates = $('.identifier-detail .sub-title')[0].innerHTML.match(/(\d{1,2} \w{3,4} \d{4}) - (\d{1,2} \w{3,4} \d{4})/)
  726.  
  727. let from = dateFormat( getDates[1].split(' ') )
  728. let to = dateFormat( getDates[2].split(' ') )
  729.  
  730. storage.tenancyStartDate = from
  731. storage.tenancyEndDate = to
  732.  
  733. function dateFormat(date) {
  734. let month = new Date(1900 + date[1] + 1).getMonth() + 1
  735. return date[2] + '-' + String(month).padStart(2, '0') + '-' + String(date[0]).padStart(2, '0')
  736. }
  737. let readable = differenceBetweenDates(from, to)[1]
  738. .replace(/&ensp;/g, '')
  739. .replace(/<b>/g, '')
  740. .replace(/<\/b>/g, '')
  741. .replace(/\s\s+/g, ' ')
  742. .replace(/ and 0 days/g, '')
  743. .replace(/, 0 weeks/g, '')
  744. .trim()
  745. let readableWrapped = `<span style="color: #8ba2af" class="readableDateRange">&ensp;▶&ensp;${readable}</span>`
  746. $('.identifier-detail .sub-title')[0].innerHTML = $('.identifier-detail .sub-title')[0].innerHTML
  747. .replace(/(\d{1,2} \w{3,4} \d{4} - \d{1,2} \w{3,4} \d{4})/, '$1' + readableWrapped)
  748. }
  749.  
  750. function currentTime(){
  751. let time = new Date();
  752. return time.toLocaleString('en-GB', { hour: 'numeric', minute: 'numeric', hour12: true });
  753. }
  754.  
  755. function currentDate() {
  756. let date = new Date
  757.  
  758. let dateYear = date.getFullYear()
  759. let dateMonth = String(date.getMonth() + 1).padStart(2, '0')
  760. let dateDate = String(date.getDate()).padStart(2, '0')
  761.  
  762. let today = `${dateYear}-${dateMonth}-${dateDate}`
  763.  
  764. return today
  765. }
  766.  
  767. function currentDateLegacy(){
  768. let date = new Date();
  769. let dateString = `${date.getDate().toString().padStart(2, '0')}/${(date.getMonth() + 1).toString().padStart(2, "0")}`;
  770. return dateString;
  771. }
  772.  
  773. function differenceBetweenDates(from, to) {
  774. let a = moment(from, 'YYYY-MM-DD');
  775. let b = moment(to, 'YYYY-MM-DD');
  776.  
  777. let diffDays = b.diff(a, 'days') + 1;
  778.  
  779. let diff = moment.duration(b.diff(a))
  780. diff.add(1, 'days')
  781.  
  782. let years = diff.years()
  783. let months = diff.months()
  784. let weeks = diff.weeks()
  785. let days = diff.days()%7
  786.  
  787. let diffFull = `<b> ${years == 1 ? years + ' year,&ensp;' : years > 1 ? years + ' years,&ensp;' : ''}
  788. ${months == 1 ? months + ' month,&ensp;' : months > 1 ? months + ' months,&ensp;' : ''}
  789. ${isNaN(weeks) ? '0 weeks' : weeks == 1 ? weeks + ' week' : weeks + ' weeks'}</b> and <b>
  790. ${isNaN(days) ? '0 days' : days == 1 ? days + ' day' : days + ' days'}</b>`
  791.  
  792. return [diffDays, diffFull];
  793. }
  794.  
  795. function querySeperator() {
  796. setTimeout( () => delay(), 128)
  797. function delay() {
  798. let commaDelimited = $('#_q').val().trim().split(' ').join(', ')
  799. $('#_q').val( commaDelimited )
  800. }
  801. }
  802.  
  803. function copyOnClick(e) {
  804.  
  805. tenant()
  806. function tenant() {
  807. if ( $(e.target).closest('.identifier-detail').length ) {
  808.  
  809. if ( e.ctrlKey && e.altKey ) {
  810. landingDetailFull()
  811. return;
  812. }
  813.  
  814. landingDetail()
  815. }
  816. if ( $(e.target).closest('.financial-transaction').length ) {
  817. landingBalance()
  818. }
  819. if ( $(e.target).closest('.summary-group-container').length ) {
  820. landingSummary()
  821. }
  822. if ( $(e.target).closest('.notes tbody').length ) {
  823. landingNotes()
  824. }
  825. if ( $(e.target).closest('.transactions tbody').length ) {
  826. statement()
  827. }
  828. if ( $(e.target).closest('.attachments tbody').length ) {
  829. documents()
  830. }
  831. }
  832.  
  833. tenantSearch()
  834. function tenantSearch() {
  835. if ( $(e.target).closest('.datatable-index-filters .tenancies tbody').length ) {
  836. tenantSearchTable()
  837. }
  838. }
  839.  
  840. // further test
  841. function landingDetail() {
  842.  
  843. if ( $(e.target).attr('class') == 'title' ) {
  844. copyToClipboard($(e.target).text().match(/TE\d{4,5}/)[0])
  845. copyToast('tenancy reference')
  846. }
  847. if ( $(e.target).attr('class') == 'name' ){
  848. let clone = $(e.target).clone()
  849. clone.find('.label').remove()
  850. copyToClipboard(clone.text().replace(/\+.+/, '').trim())
  851. copyToast('full name')
  852. }
  853. if ( $(e.target).closest('.sub-title').length ) {
  854. copyToClipboard($('.sub-title a')[0].innerHTML.split(',')[0].replace(' Room', ', Room'))
  855. copyToast('property + room')
  856. }
  857. }
  858. function landingDetailFull() {
  859.  
  860. let clone = $('.identifier-detail .name').clone()
  861. clone.find('.label').remove()
  862. let getName = clone.text().replace(/\+.+/, '').trim()
  863. let getDate = $('.identifier-detail').children().eq(2).contents()[2].wholeText
  864. let getMonthly = $('.content-main .summary-data-container').children().eq(1).children().eq(1).text().trim()
  865.  
  866. copyToClipboard(getName + "\n" + getDate + "\n" + getMonthly + "\n\n")
  867. copyToast('full information')
  868. }
  869. function landingBalance() {
  870. if ( $(e.target).attr('class').includes('number') ) {
  871. copyToClipboard($(e.target).text())
  872. copyToast('balance')
  873. }
  874. }
  875. function landingSummary() {
  876. if ( $(e.target).closest('.name').length ) {
  877. copyToClipboard($(e.target).text())
  878. copyToast('full name')
  879. }
  880. if ( $(e.target).closest('.detail > div:nth-child(2) div').length ) {
  881. copyToClipboard($(e.target).text())
  882. copyToast('phone')
  883. }
  884. if ( $(e.target).closest('.summary-group-container .detail > div:nth-child(3) div').length ) {
  885. copyToClipboard($(e.target).text())
  886. copyToast('email')
  887. }
  888. }
  889. function landingNotes() {
  890. if ( $(e.target).closest('.note').length ) {
  891. copyToClipboard($(e.target).text())
  892. copyToast('note')
  893. }
  894. }
  895. function statement() {
  896. if ( $(e.target).closest('.ref').length ) {
  897. copyToClipboard($(e.target).text())
  898. copyToast('ref')
  899. }
  900. if ( $(e.target).closest('.date').length ) {
  901. copyToClipboard($(e.target).text().split('Due')[0] )
  902. copyToast('date')
  903. }
  904. if ( $(e.target).closest('.description').length ) {
  905. copyToClipboard($(e.target).closest('.description').text().replace(/ {3}.+/, '').trim() )
  906. copyToast('description')
  907. }
  908. if ( $(e.target).closest('.charge-made').length ) {
  909. copyToClipboard($(e.target).text().trim())
  910. copyToast('charge made')
  911. }
  912. if ( $(e.target).closest('.payments-received').length ) {
  913. copyToClipboard($(e.target).text().trim())
  914. copyToast('received')
  915. }
  916. if ( $(e.target).closest('.outstanding').length ) {
  917. copyToClipboard($(e.target).text().trim())
  918. copyToast('outstanding')
  919. }
  920. }
  921. function documents() {
  922. if ( $(e.target).closest('.name').length ) {
  923. let header = $(e.target).closest('.name').find('a').text()
  924. let body = $(e.target).closest('.name').find('.unimportant')[0].innerHTML.replace(/<br>/g, '\n')
  925. let compile = body ? header + '\n' + body : header;
  926.  
  927. let headerLowerCase = header.toLowerCase()
  928.  
  929. if ( headerLowerCase.includes('top') || headerLowerCase.includes('receipt') || headerLowerCase.includes('key') ) {
  930.  
  931. let bodyArr = []
  932.  
  933. for ( let i = 0; i < $(targets.documents.checkboxes).length - 1; i++ ) {
  934.  
  935. // if anything is checked , return
  936. if ( $(targets.documents.checkboxes)[i].checked ) {
  937.  
  938. // as something is checked, we want to loop over checked and push its data
  939. for ( let i = 0; i < $(targets.documents.checkboxes).length; i++ ) {
  940. if ( $(targets.documents.checkboxes)[i].checked ) {
  941. bodyArr.push( $($(targets.documents.checkboxes)[i].closest('tr')).find('.unimportant')[0].innerHTML.replace(/<br>/g, '\n') )
  942. }
  943. }
  944.  
  945. compile = header + '\n' + bodyArr.join('\n')
  946.  
  947. copyToClipboard( noteFormat_topup(compile) )
  948. copyToast('multiple utility notes')
  949. return;
  950. }
  951. }
  952. copyToClipboard( noteFormat_topup(compile) )
  953. copyToast('utility note')
  954. } else {
  955. copyToClipboard(compile)
  956. copyToast('note')
  957. }
  958. }
  959. }
  960. function tenantSearchTable() {
  961. if ( $(e.target).closest('.name').length ) {
  962. copyToClipboard($(e.target).text())
  963. copyToast(`ref`)
  964. }
  965. if ( $(e.target).closest('.tenant').length ) {
  966. copyToClipboard($(e.target).closest('.tenant').text().replace(/ {2}.+/, '').split('+')[0].trim())
  967. copyToast('name')
  968. }
  969. if ( $(e.target).closest('.unit-address').length ) {
  970. copyToClipboard($(e.target).text())
  971. copyToast('unit-address')
  972. }
  973. }
  974.  
  975.  
  976. }
  977.  
  978. function noteFormat_topup( content ) {
  979.  
  980. console.log(content)
  981.  
  982. let split = content.split('\n')
  983. let header = split[0] + ' ― '
  984. let body = split.slice(1).join('; ')
  985.  
  986. console.log( (header + body + ';').replace(/ ;/g, '') )
  987.  
  988. return (header + body + ';').replace(/ ;/g, '')
  989. }
  990.  
  991. function copyToClipboard(text) {
  992. let temp = $("<textarea>");
  993. $('body').append(temp);
  994. temp.val(text).select();
  995. document.execCommand("copy");
  996. temp.remove();
  997. }
  998.  
  999. function copyToast(text) {
  1000.  
  1001. if ( $('.copyToast').length > 4 ) return;
  1002.  
  1003. let x = event.pageX
  1004. let y = event.pageY
  1005.  
  1006. $(window).on('mouseup', calcMousePos)
  1007.  
  1008. function calcMousePos(newMousePos) {
  1009.  
  1010. if (Math.abs(x - newMousePos.pageX) > 1 || Math.abs(y - newMousePos.pageY) > 1) return;
  1011.  
  1012. let elem = document.createElement('div')
  1013. elem.innerHTML = icons.clipboard + ' ' + text
  1014. elem.className = "copyToast"
  1015. elem.style.backgroundColor = $('#main_nav').css('background-color')
  1016. elem.style.left = event.pageX + "px"
  1017. elem.style.top = event.pageY - 25 + "px"
  1018. $('body').append(elem);
  1019. $(elem).delay(618).animate({
  1020. 'opacity': 0,
  1021. 'top': event.pageY - 55 + "px",
  1022. }, 618, function() {
  1023. $(elem).remove()
  1024. })
  1025.  
  1026. $(window).off('mouseup', calcMousePos)
  1027. }
  1028. }
  1029.  
  1030. function disableAccess(desc, remove) {
  1031.  
  1032. if ( remove ) {
  1033. removeDisableAccess()
  1034. return;
  1035. }
  1036. if ( $('#disableAccess').length ) return;
  1037.  
  1038. $('body').append(` <div id="disableAccess">
  1039. <div id="disableDesc">${desc}</div>
  1040. </div>`)
  1041.  
  1042. $('#disableAccess').hide().fadeIn(618)
  1043.  
  1044. setTimeout( () => {
  1045. if ( $('#disableAccess').length ) {
  1046. $('#disableDesc').append('<br><div class="btn" style="transform: scale(1.75,1.75); margin-top: 48px" id="disableExit">This is taking too long! Get me out of here. 😠</div>')
  1047. $('#disableExit').hide().fadeIn(1024)
  1048. $('#disableExit').on('click', function() {
  1049. removeDisableAccess()
  1050. })
  1051. }
  1052. }, 5500)
  1053.  
  1054. $('#disableClickCover').on('mousedown keydown', disableAccess)
  1055. function disableAccess(e){
  1056. e.preventDefault()
  1057. return;
  1058. }
  1059.  
  1060. function removeDisableAccess() {
  1061. $('#disableAccess').off('mousedown keydown scroll', disableAccess)
  1062. $('#disableAccess').fadeOut(314, function() {
  1063. $('#disableAccess').remove()
  1064. })
  1065. }
  1066.  
  1067. }
  1068.  
  1069. function quickAccess() {
  1070.  
  1071. let html = `<div class="qaOption report" id="qaReport">
  1072. <div style="position: fixed; left: 27px">${icons.letter}</div>
  1073. Overdue Report</div>`
  1074.  
  1075. $('#main_nav').append(html)
  1076.  
  1077. $('.qaOption').on('click', qaActions)
  1078.  
  1079. function qaActions(e) {
  1080. if (e.target.id == 'qaReport'){
  1081. qaGenOverdueReport()
  1082. }
  1083. }
  1084.  
  1085. function qaGenOverdueReport() {
  1086. disableAccess('<b style="font-size: 1.5em;">Please wait 📋</b><br>Generating overdue report...')
  1087.  
  1088. let iframe = document.createElement('iframe')
  1089. iframe.id = 'targetIframe'
  1090. iframe.src = 'https://system.arthuronline.co.uk/flintonsprop/reports/edit/11441/'
  1091.  
  1092. $('body').append(iframe)
  1093.  
  1094. let interval;
  1095. if ( !$(iframe).contents().find("body").length ) {
  1096. interval = setInterval( () => findBody(), 25)
  1097. }
  1098. function findBody() {
  1099. if ( $(iframe).contents().find("body").length ) {
  1100. clearInterval(interval)
  1101. iframeContents()
  1102. }
  1103. }
  1104.  
  1105. setTimeout( () => iframeContents(), 3000)
  1106.  
  1107. function iframeContents() {
  1108. let contents = $(iframe).contents()
  1109. contents.find('.daterangepicker .ranges ul li:nth-child(3)').click()
  1110. contents.find('#runOutput')[0].selectedIndex = 4;
  1111. contents.find('.save-and-run .btn').click()
  1112. setTimeout( () => iframe.remove(), 5000 )
  1113. disableAccess('', true)
  1114. }
  1115. }
  1116. }
  1117.  
  1118. function quickNotes() {
  1119. let html = `<div id="quickNotes">
  1120. <div id="qnPhone" class="btnCirc">${icons.phone}</div>
  1121. <div id="qnVoicemail" class="btnCirc">${icons.vm}</div>
  1122. <div id="qnEmail" class="btnCirc">${icons.email}</div>
  1123. <div id="qnReceipt" class="btnCirc">${icons.receipt}</div>
  1124. </div>`
  1125.  
  1126. $('#content-wrapper').append(html)
  1127.  
  1128. $('#quickNotes').on('mousedown', quickNoteActions)
  1129. function quickNoteActions(e) {
  1130.  
  1131.  
  1132. if ( e.target.closest('div').id == 'qnPhone') {
  1133. copyToClipboard(`PHONED tenant at ${currentTime()} on ${currentDateLegacy()} regarding `)
  1134. }
  1135. if ( e.target.closest('div').id == 'qnVoicemail') {
  1136. copyToClipboard(`Left VOICEMAIL at ${currentTime()} on ${currentDateLegacy()} regarding `)
  1137. }
  1138. if ( e.target.closest('div').id == 'qnEmail') {
  1139. copyToClipboard(`EMAILED tenant at ${currentTime()} on ${currentDateLegacy()} regarding `)
  1140. }
  1141. if ( e.target.closest('div').id == 'qnReceipt') {
  1142. let tenantPayDay = String(storage.tenancyStartDate.match(/\d{4}-\d{1,2}-(\d{1,2})/)[1]).padStart(2, '0')
  1143. copyToClipboard(`Top-up receipts approved to be deducted from rent due ${tenantPayDay}/`)
  1144. }
  1145. }
  1146. }
  1147.  
  1148. function addTransactionBtn(e) {
  1149. if ( e.target.closest( targets.addTransaction.btn ) ) {
  1150. topupProcessing()
  1151. }
  1152.  
  1153. function topupProcessing() {
  1154. let desc;
  1155. let date;
  1156. let amount;
  1157.  
  1158. new Promise( function(resolve) {
  1159. waitForIframeExistance('#dialog iframe', targets.addTransaction.amount , resolve)
  1160. }).then( () => {
  1161. //$('#dialog iframe').contents().find( targets.addTransaction.desc ).on('change', descChange)
  1162. $('#dialog iframe').contents().find( targets.addTransaction.desc ).on('paste', function() {
  1163. setTimeout( () => descChange(), 32)
  1164. })
  1165.  
  1166. desc = $('#dialog iframe').contents().find( targets.addTransaction.desc )
  1167. date = $('#dialog iframe').contents().find( targets.addTransaction.date )
  1168. amount = $('#dialog iframe').contents().find( targets.addTransaction.amount )
  1169. } )
  1170.  
  1171. function descChange() {
  1172. let current = currentDate().split('-')
  1173. let dayAndMonth = desc.val().match(/(?<=deduct).*?(\d{1,2})[/.-](\d{1,2})/)
  1174. let day = dayAndMonth[1]
  1175. let month = dayAndMonth[2]
  1176. let year = current[0]
  1177. if ( +current[1] == 12 && +month == 1 ) {
  1178. year++
  1179. }
  1180. date.val(`${year}-${month}-${day}`)
  1181.  
  1182. parseDesc()
  1183. }
  1184. function parseDesc() {
  1185.  
  1186. let topUps = desc.val().split(' ― ')[1].split(';')
  1187. topUps.splice(topUps.length - 1, 1)
  1188. let arr = []
  1189.  
  1190. for(let i = 0; i < topUps.length; i++) {
  1191. console.log(topUps[i])
  1192.  
  1193. if (topUps[i].includes('dup')) {
  1194. continue;
  1195. } else if (topUps[i].includes('app') && topUps[i].includes('rej')) {
  1196.  
  1197. let match
  1198. try {
  1199. match = topUps[i].match(/(?<=app\w+) £?((\d+)(\.?(\d+))?)/)[1]
  1200. } catch(err) {
  1201. match = topUps[i].match(/((\d+)(\.?(\d+))?)(?= app)/)[1]
  1202. }
  1203. arr.push( +match )
  1204.  
  1205. } else if (topUps[i].includes('rej')) {
  1206. continue;
  1207. } else {
  1208. arr.push( +topUps[i].match(/£((\d*)\.?(\d*))/)[1] )
  1209. }
  1210. }
  1211.  
  1212. // populate value field
  1213. if ( arr.length == 1) {
  1214. amount.val( -arr[0] )
  1215. } else {
  1216. amount.val( -arr.reduce( (prev, curr) => prev + curr ) )
  1217. }
  1218. }
  1219. }
  1220. }
  1221.  
  1222. function loadIcons() {
  1223. icons.percent = `<img style="filter: invert(100%); height: 20px; width: 20px" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA9klEQVRIie2UOw6CQBRFj53f1sRVuAoLURtWopWfljXgWlyCn87EPRjssLFCi7lGVJCBUFB4EkLezHDfm5l3gT9VZQTsgaveTsq6BuAWEb8nPElJfM15eRIc9NEcaAELxbuPdRMgAm5AP0+CUIJtxR3FYWxNDwg0PssjDubM78BSSVa876AGbDS2UZyLAWbrn3cw1PxM8UU7KYSjiq/ANibex5x5hLmDUqkDR0z1ftniAGuJn4Bm2eJJLWlrxky6wBlT/VRjY+zN+JO0lrQ1YybPlgx4b0kbM2byqyWzzGiFR3pLOnyff8TLL9a4mF9yEmlm/FMxHpirXwLh4+GPAAAAAElFTkSuQmCC">`,
  1224. icons.clipboard = `<img style=" width: 14px; height: 14px; margin-top: -2px" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAAgUlEQVRIie2VQQqAIBBFn9ExovtE9+ji0z2mTYJKaE1Zi/wgunDmyf8wOlWlprqq3RPADKyAGtehXGCRAMONx7oSQHMXrXo1g1TWTASYfJOcRXcyEWAsAayZRHWfZtAADfAXQB+cH52iXp9atO675WeTM4AlgFyR7LVAPE2rqHoGG9+QR9qv/pARAAAAAElFTkSuQmCC">`,
  1225. icons.copy = `<img class="genCopyIcon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABHNCSVQICAgIfAhkiAAAAWBJREFUaIHtmKFOA0EQhv/ZrihJBRKBRFTUInB9BUjqEVUXDCEhQaArCgpNcr6iBolENEFgKnmAPsCFJg13g2pCZo+EvfRuuDCf29nkbr7M7k1uAMP431BZcJQkvXzdnRQFhkQ0aDKheXpfmtNPeBkYJUlv87H36oA+RT1KBycD+bo7cUBfI5kqBAJFgaFGIlUJjpA887FnsmmCCrQNE9AmuAO/ock+wcxLcrTw7G5n6XQl96MrsO0TzHTRRJMjogEY4w3yt7Px9aHcjxbQ6hMOdIDP/CGMR6LZJxjhu6PvQNN94vT8ir8t9+V+679CJqCNCWhjAtqYgDYmoI0JaGMC2piANiagTfBP/NdnoZLWV8AEtImfCzHemXC0XYu5Ta0wI5Ox+AoQXnaSTTUWMhAt0IG7KcDBlLhumJGRp0sZjxaYpdNVx/tjMJ7KSrprmJEx45k8ncwf75Z1v88w2sYX2olePMs5UyEAAAAASUVORK5CYII=">`,
  1226. icons.letter = `<img style="width: 17px; height: 17px;margin-top: -1px" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAQAAAD8x0bcAAAAcElEQVR4AZ3KoRGAMBBE0YNuSBO0gsEhY2FSC5XQAS5DJ6hkITHczawA/rdPPoQOQT1wdODpBOAZ0m3whnEkgskwiu4wIltm0Y6+vgIW8f6gXG/UuczQouaIbVGqt+pUZmhWc8S2KIIVLXIIZCfvuwD++D9vDI3s3wAAAABJRU5ErkJggg==">`,
  1227. icons.phone = `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAQAAABLCVATAAABA0lEQVR4Ae3UMSgFUBTH4SNPYWGhTDLJbBPCoIyCwV7CYLAYhDJaTRY2MSiSwURZpNjLCqO32PQx6d0yvHfv+r6z/+os/2hqhBW7f7elM/KYkzqIPDalHiKPVamXyGNJqhp5jCA1GDn0SLEceXxIHUYex1LrkceiWjfaIo8OXwCutEU+Z4ArlShhEjAQpTyC6yhlHDAbpRyBd31RRrd38Kwjyhj1DS6ilDXAmfaoocWOfRMqUS/bgCe9NZkTQNWdW7dO63jfHuDVQvzS6kQK1hob33vDzv1nN+phxqdEA6GUfpcFoZQZTwWhlDHHPrNDKRXTDrypNR/5dBkyZcmG1WhqwA+OqfZX7C8frQAAAABJRU5ErkJggg==">`,
  1228. icons.vm = `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAQAAABLCVATAAABh0lEQVR4Ae3TM6CWURgA4DfbXLKnzKk127Ybs+227DlrryXXFsZsm0/+6r/hYonPuxz7xG/pv/8UMsR+Fz1x3HL14/tCQ8sd99hF+wxRIFLp75ZU25WKNJSyXaob+sYXFvieq6rGV1Rz1fcsiI8MAFzQS/YI5a31BpyTLF1B58Aba5WPkF0vFwADIkJZD8EauSOhsZtgcVKyFNzUOBLyWA0eKhtmg6OyRQotwXP54h35PAetIoVsDoPZ4SToEd9wFrSJd7QFZ7/Tqjs4Fe6Ast9pshKMjneMBau+06oMuBOeg/zfabIQjI93TASLvtMqH3gRLoK632myM7mRMAjs+k6rOuBi2Pb9uRT1FFSLd9QATxWNNCwC20JH8EitSGELOJjkj4LNkUJNj0CniHAA3DNM9vhAFXsBzZIuzQF7VYwP5DDSPZLplHYZcNYsM2zwAnDRDEm4BHhhgxlmOQu4kvxKFZ2RVWdUTL3E8S7JrEvGyxtpyaaRQaabkYGYZpCGssV///3l3gLdS5QBUjZf7QAAAABJRU5ErkJggg==">`,
  1229. icons.email = `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAQAAABLCVATAAAA00lEQVR4Ae3QAQbCUBzH8X8FpBOkMwQdIoi6SZ0ghlSgblIIO0RhZ8hjAyEB1Tf+FP/ZHnvAsg/Y4/lu+0mdNYhIqSolomUzK0JtbcgR6mZDLwAmFYKOCaogJEKPnT77PdnTFSkPKYYk+CQMRZWGONHXU4c5D4rcWdDWO31O5SF7MSYvLnqRDb0LP32K4+vKzP66P2TG/I2fP+MPWY6xKEaMRDHGoSqE1FE3UQw4oPyhMjq+HTcopC6cUeEhqwn9bSglVGpDa0JtxGJJRlUZkTRq7ANaQCOJt++IMQAAAABJRU5ErkJggg==">`,
  1230. icons.receipt = `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAQAAABLCVATAAAAgklEQVR4Ae2RAQaAQBBF9x6hPVrRETrAANCpooMUAUQ3eLEGaJkJonb/4I+HBz8UEHqa1IKkjswO0t1FwkaDADASWcFBJCeCEw2HtkWyoscpWCT2uUTBkXdFiH11tbpaXa2u9hXRiUY/m2RFK5FJd2vZwSY5UUdMPSGpWxYHGcL/cwGEQwsHbI/lEwAAAABJRU5ErkJggg==">`
  1231. }
  1232.  
  1233. function appendCSS() {
  1234.  
  1235. const colors = {
  1236. red: '#d64a65',
  1237. blue: '#1facf0',
  1238. green: '#8BC34A',
  1239. }
  1240.  
  1241. const copyToast = `.copyToast{
  1242. position: absolute;
  1243. color: white;
  1244. font-family: 'Open Sans', sans-serif;
  1245. text-transform: uppercase;
  1246. font-size: 0.75rem;
  1247. font-weight: 600;
  1248. padding: 4px 9px;
  1249. border-radius: 12px;
  1250. transform: translate(-50%, -50%);
  1251. z-index: 9999;
  1252. pointer-events: none;
  1253. }`
  1254.  
  1255. const tenantHeaderTweaks = `.left-side-detail > :nth-child(3){
  1256. margin-right: -40px;
  1257. }
  1258. .left-side-detail{
  1259. width: calc(50% + 200px) !important;
  1260. }`
  1261.  
  1262. const tenantToolbox = ` #tenantToolbox {
  1263. width: 240px;
  1264. }
  1265. #tenantToolbox .option{
  1266. display: flex;
  1267. cursor: pointer;
  1268. user-select: none;
  1269. margin-bottom: 7px;
  1270. transition: 314ms;
  1271. }
  1272. #tenantToolbox .option:hover{
  1273. filter: brightness(125%);
  1274. }
  1275. #tenantToolbox .option:active{
  1276. filter: brightness(70%) contrast(250%);
  1277. transition: 32ms;
  1278. }
  1279. #tenantToolbox .icon{
  1280. display: flex;
  1281. align-items: center;
  1282. justify-content: center;
  1283. width: 22px;
  1284. height: 22px;
  1285. border-radius: 4px;
  1286. margin-right: 10px;
  1287. background: dimgray;
  1288. }
  1289. #tenantToolbox span{
  1290. padding-top: 1px;
  1291. font-weight: 600;
  1292. font-size: 15px;
  1293. color: dimgray;
  1294. }
  1295.  
  1296. #tenantToolbox .icon.red{
  1297. background: #DA3C5A;
  1298. }
  1299. #tenantToolbox span.red{
  1300. color: #DA3C5A;
  1301. }`
  1302.  
  1303. const tenantSummaryTweaks = `* {
  1304. outline: 0px dashed rgba(0, 0, 256, 0.1);
  1305. }
  1306. .retailspace-horizontal .header {
  1307. display: none;
  1308. }
  1309. .summary-wrapper hr:nth-child(2) {
  1310. display: none;
  1311. }
  1312. .summary-group-container .detail.detail div:first-child,
  1313. .summary-group-container .detail.detail div:nth-child(4),
  1314. .summary-group-container .detail.detail div:nth-child(5) {
  1315. display: none;
  1316. }
  1317. .summary-group-container .detail div:nth-child(2) div {
  1318. font-size: 2.3rem !important;
  1319. letter-spacing: 0.1618rem;
  1320. padding: 18px 0 16px 0;
  1321. margin-left: -2px;
  1322. }
  1323. .summary-group-container .detail div:nth-child(3) div {
  1324. font-size: 1.2rem !important;
  1325. padding: 12px 0 0 0;
  1326. }
  1327. .summary-group-container .status-container div {
  1328. display: none;
  1329. }`
  1330.  
  1331. const tenantReadabilityTweaks = `.numeric {
  1332. font-size: 1.218em;
  1333. font-weight: 600;
  1334. }`
  1335.  
  1336. const statementReport = `.btnSpecial {
  1337. position: fixed;
  1338. z-index: 1001;
  1339. left: 240px;
  1340. bottom: 18px;
  1341. outline: 0;
  1342. }
  1343. .btnPad{
  1344. padding: 11px;
  1345. }
  1346. .textareaSpecial {
  1347. position: fixed;
  1348. font-family: 'Open Sans', sans-serif;
  1349. box-sizing: border-box;
  1350. font-size: 16px;
  1351. line-height: 2em;
  1352. bottom: 62px;
  1353. z-index: 1000;
  1354. left: 240px;
  1355. resize: none;
  1356. padding: 6px 10px;
  1357. color: #516073;
  1358. background: #f5f7fa;
  1359. white-space: nowrap;
  1360. }
  1361. .textareaSpecial:focus {
  1362. outline: 0;
  1363. box-shadow: -40px 70px 175px rgba(0,0,0,0.15), 0 8px 12px rgba(0,0,0,0.14) !important;
  1364. border: 1px solid #8ba2af;
  1365. }
  1366. .textareaSpecial::selection{
  1367. background: #8ba2af;
  1368. color: white;
  1369. }
  1370. .genCopyIcon {
  1371. position: fixed;
  1372. bottom: 78px;
  1373. z-index: 1001;
  1374. filter: brightness(170%);
  1375. }
  1376. .genCopyIcon:hover {
  1377. filter: hue-rotate(350deg) brightness(140%) contrast(300%);
  1378. }
  1379. .genCopyIcon:active {
  1380. filter: none;
  1381. }`
  1382.  
  1383. const modal = ` #fModal{
  1384. font-family: 'Open Sans', sans-serif;
  1385. transform: translate(-50%, 0%);
  1386. position: fixed;
  1387. top: 15%;
  1388. left: 50%;
  1389. z-index: 2001;
  1390. width: 460px;
  1391. }
  1392. #fModal .header{
  1393. display: flex;
  1394. align-items: center;
  1395. background: dimgray;
  1396. height: 68px;
  1397. font-size: 20px;
  1398. font-weight: 400;
  1399. color: white;
  1400. padding-left: 24px;
  1401. border-radius: 6px 6px 0 0;
  1402. cursor: grab;
  1403. user-select: none;
  1404. box-shadow: 0 8px 24px rgba(0,0,0,0.05), 0 8px 4px rgba(0,0,0,0.1618);
  1405. }
  1406. #fModal .header:active{
  1407. cursor: grabbing;
  1408. }
  1409. #fModal .body{
  1410. border-bottom: 1px solid #ECECEC;
  1411. padding: 14px 24px;
  1412. font-size: 16px;
  1413. background: #fff;
  1414. line-height: 2.75rem;
  1415. letter-spacing: 0.314px;
  1416. box-shadow: 0 8px 24px rgba(0,0,0,0.05), 0 8px 4px rgba(0,0,0,0.1618);
  1417. }
  1418. #fModal .body hr{
  1419. margin: 16px -24px 10px -24px;
  1420. border: 0.5px solid #ececec;
  1421. }
  1422. #fModal .footer{
  1423. display: flex;
  1424. justify-content: space-between;
  1425. padding: 12px 24px;
  1426. height: 52px;
  1427. background: #fff;
  1428. border-radius: 0 0 6px 6px;
  1429. box-shadow: 0 8px 24px rgba(0,0,0,0.05), 0 4px 4px rgba(0,0,0,0.1618);
  1430. }
  1431. #fModal .left{
  1432. align-self: center;
  1433. font-size: 16px;
  1434. margin-top: -4px;
  1435. }
  1436. #fModal .right{
  1437. align-self: center;
  1438. }
  1439. #fModal .btn{
  1440. background: dimgray;
  1441. user-select: none;
  1442. border-radius: 3px;
  1443. padding: 11px 15px;
  1444. margin-left: 12px;
  1445. }
  1446. #fModal .btn:hover{
  1447. filter: brightness(115%);
  1448. }
  1449. #fModal .btn:active{
  1450. filter: brightness(80%) contrast(150%);
  1451. transition: 64ms;
  1452. }
  1453. #fModal b{
  1454. cursor: default;
  1455. letter-spacing: 0.618px;
  1456. border-bottom: 1px dashed gainsboro;
  1457. padding: 0 4px 4px 4px;
  1458. }
  1459. #fModal b.start{
  1460. padding: 0 4px 4px 0;
  1461. }`
  1462.  
  1463. const modalInputs = `#fModal input::-webkit-inner-spin-button,
  1464. #fModal input::-webkit-outer-spin-button{
  1465. -webkit-appearance: none;
  1466. margin: 0;
  1467. }
  1468. #fModal input::-webkit-calendar-picker-indicator{
  1469. opacity: 0.75;
  1470. padding: 6px 0 6px 6px;
  1471. margin-left: -12px;
  1472. }
  1473. #fModal input::-webkit-clear-button{
  1474. display: none;
  1475. }
  1476. #fModal input[type="date"]::-webkit-calendar-picker-indicator {
  1477. width: 10px;
  1478. height: 10px;
  1479. }
  1480. #fModal input{
  1481. position: relative;
  1482. top: 6px;
  1483. font-size: inherit;
  1484. font-family: inherit;
  1485. font-weight: 700;
  1486. border: 0;
  1487. outline: 0;
  1488. border-radius: 0;
  1489. box-shadow: none;
  1490. border-bottom: 1px solid gray;
  1491. transition: 0.6s cubic-bezier(0.075, 0.82, 0.165, 1);
  1492. letter-spacing: 0.618px;
  1493. padding: 0 2px 4px 2px;
  1494. cursor: pointer;
  1495. }
  1496. #fModal input:hover{
  1497. top: 8px;
  1498. padding-bottom: 8px;
  1499. }
  1500. #fModal input:focus{
  1501. top: 8px;
  1502. color: dimgray;
  1503. border-bottom: 2px solid dimgray;
  1504. padding: 0 8px 7px 8px;
  1505. }
  1506. #fModal .percent{
  1507. margin-top: -1px;
  1508. padding-top: -1px;
  1509.  
  1510. }`
  1511.  
  1512. const modalColorVariation = ` #fModal .header.red{
  1513. background: ${colors.red};
  1514. }
  1515. #fModal .header.blue{
  1516. background: ${colors.blue};
  1517. }
  1518. #fModal .header.green{
  1519. background: ${colors.green};
  1520. }
  1521. #fModal .btn.red{
  1522. background: ${colors.red};
  1523. }
  1524. #fModal .btn.blue{
  1525. background: ${colors.blue};
  1526. }
  1527. #fModal .btn.green{
  1528. background: ${colors.green};
  1529. }
  1530. #fModal.red *::selection{
  1531. background: ${colors.red} !important;
  1532. color: white;
  1533. }
  1534. #fModal.blue *::selection{
  1535. background: ${colors.blue} !important;
  1536. color: white;
  1537. }
  1538. #fModal.green *::selection{
  1539. background: ${colors.green} !important;
  1540. color: white;
  1541. }
  1542. #fModal input.red:focus{
  1543. color: ${colors.red};
  1544. border-bottom: 2px solid #d64a65;
  1545. }
  1546. #fModal input.blue:focus{
  1547. color: ${colors.blue};
  1548. border-bottom: 2px solid #d64a65;
  1549. }
  1550. #fModal input.green:focus{
  1551. color: ${colors.green};
  1552. border-bottom: 2px solid #d64a65;
  1553. }
  1554. #fModal .totalStyle{
  1555. padding-bottom: 6px;
  1556. border-bottom: 1px solid #d64a65;
  1557. }`
  1558. const disableAccess = `#disableAccess {
  1559. display: flex;
  1560. position: fixed;
  1561. top: 0;
  1562. left: 0;
  1563. align-items: center;
  1564. justify-content: center;
  1565. color: lightgray;
  1566. background: rgba(0,6,12,0.65);
  1567. height: 100vh;
  1568. width: 100vw;
  1569. z-index: 9999;
  1570. cursor: wait;
  1571. user-select: none;
  1572. backdrop-filter: blur(4px);
  1573. }
  1574. #disableDesc {
  1575. transition: 314ms;
  1576. font-family: 'Open Sans', sans-serif;
  1577. font-size: 3.5em;
  1578. line-height: 1.5em;
  1579. text-align: center;
  1580. margin-top: -128px;
  1581. }`
  1582.  
  1583. const quickAccess = `.qaOption.report{
  1584. box-sizing: border-box;
  1585. position: absolute;
  1586. font-size: inherit;
  1587. transition: color 314ms;
  1588. background: #0693d7;
  1589. padding-left: 68px;
  1590. padding-bottom: 18px;
  1591. padding-top: 12px;
  1592. bottom: 0;
  1593. width: 220px;
  1594. cursor: pointer;
  1595. }
  1596. .qaOption:hover{
  1597. color: white;
  1598. padding-left: 63px;
  1599. border-left: 5px solid #ffffff;
  1600. }
  1601. .qaOption.report:active{
  1602. font-weight: bold;
  1603. }
  1604. nav div ul li a[title="Settings"]{
  1605. padding-bottom: 34px;
  1606. }
  1607. #targetIframe{
  1608. position: fixed;
  1609. left: 0;
  1610. top: 0;
  1611. height: 200px;
  1612. width: 200px;
  1613. }`
  1614.  
  1615. const quickNotes = `#quickNotes {
  1616. pointer-events: none;
  1617. z-index: 9999;
  1618. display: flex;
  1619. position: fixed;
  1620. bottom: 0px;
  1621. right: 76px;
  1622. padding: 1.2rem;
  1623. }
  1624. .btnCirc {
  1625. pointer-events: visible;
  1626. background: #2392EC;
  1627. color: white;
  1628. border-radius: 100%;
  1629. margin-left: 0.75rem;
  1630. transition: 128ms;
  1631. height: 45px;
  1632. width: 45px;
  1633. box-shadow: 0px 0px 18px 0px rgba(0, 0, 0, 0.15);
  1634. cursor: pointer;
  1635. }
  1636. .btnCirc:hover {
  1637. filter: brightness(120%);
  1638. transform: scale(1.055, 1.055);
  1639. }
  1640. .btnCirc:active {
  1641. transition: 64ms;
  1642. filter: brightness(85%) saturate(75%) contrast(150%);
  1643. transform: scale(0.75, 0.75);
  1644. }
  1645. .btnCirc img{
  1646. pointer-events: visible;
  1647. padding: 8px;
  1648. -webkit-user-drag: none;
  1649. transition: 314ms;
  1650. width: 28px;
  1651. height: 28px;
  1652. }
  1653. .btnCirc img:hover{
  1654. transform: scale(1.1, 1.1);
  1655. }`
  1656.  
  1657. const style = ` <style>
  1658. ${copyToast}
  1659. ${tenantHeaderTweaks}
  1660. ${tenantToolbox}
  1661. ${tenantSummaryTweaks}
  1662. ${tenantReadabilityTweaks}
  1663. ${statementReport}
  1664. ${modal}
  1665. ${modalInputs}
  1666. ${modalColorVariation}
  1667. ${disableAccess}
  1668. /* ${quickAccess} */
  1669. ${quickNotes}
  1670. </style>`
  1671.  
  1672. $('head').append(style)
  1673. }