Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
 
 
 
 
 
 

351 řádky
8.9 KiB

  1. <template>
  2. <div id="new" class="page">
  3. <h2>New Loan</h2>
  4. <section class="loans-list">
  5. <h3 v-for="(l, indx) in loans"
  6. :class="sel == indx ? 'sel' : ''"
  7. @click="() => sel = indx"
  8. >
  9. {{l.title}}
  10. </h3>
  11. <div class="add">
  12. <svg @click="create"
  13. xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
  14. class="bi bi-plus" viewBox="0 0 16 16"> <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0
  15. 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/> </svg>
  16. </div>
  17. </section>
  18. <section class="form inputs">
  19. <h3>Loan</h3>
  20. <label>Name</label>
  21. <input :value="loans[sel].title"
  22. @input="(e) => loans[sel].title = stripLetters(e)">
  23. <button @click="del">Delete</button>
  24. </section>
  25. <section class="form inputs">
  26. <div class="hint">
  27. <img class="icon" src="/assets/image/icon/question-circle.svg" alt="">
  28. <div class="tooltip">
  29. <p>Assumes borrower is not self employed, not bankrupt in the past 7
  30. years, a citizen, and intends to occupy the property.</p>
  31. </div>
  32. </div>
  33. <h3>Borrower</h3>
  34. <label>Number of Borrowers</label>
  35. <input :value="estimate.borrowers"
  36. @input="(e) => estimate.borrowers = stripInt(e)">
  37. <label>Credit Score</label>
  38. <input :value="estimate.creditScore"
  39. @input="(e) => estimate.creditScore = stripInt(e)">
  40. <label>Monthly Income ($)</label>
  41. <input :value="estimate.mIncome"
  42. @input="(e) => estimate.mIncome = strip(e)">
  43. </section>
  44. <section class="radios form">
  45. <h3>Transaction Type</h3>
  46. <input type="radio" name="transaction_type" value="estimate.transaction"
  47. @input="e => estimate.transaction = 0"
  48. selected="estimate.transaction == 0">
  49. <label>Purchase</label>
  50. <input type="radio" name="transaction_type" value="estimate.refinance"
  51. @input="e => estimate.transaction = 1"
  52. selected="estimate.transaction == 1">
  53. <label>Refinance</label>
  54. </section>
  55. <section class="form inputs">
  56. <h3>Property Details</h3>
  57. <label>Price ($)</label>
  58. <input :value="estimate.price" @input="setPrice">
  59. <label>Type</label>
  60. <select id="" name="" v-model="estimate.property">
  61. <option value="attched">Single Family Attached</option>
  62. <option value="detached">Single Family Detached</option>
  63. <option value="lorise">Lo-rise (4 stories or less)</option>
  64. <option value="hirise">Hi-rise (over 4 stories)</option>
  65. </select>
  66. </section>
  67. <section class="radios form">
  68. <h3>Loan Type</h3>
  69. <input type="radio" name="loan_type" value="conv" v-model="loans[sel].type"
  70. >
  71. <label>Conventional</label>
  72. <input type="radio" name="loan_type" value="fha" v-model="loans[sel].type">
  73. <label>FHA</label>
  74. <input type="radio" name="loan_type" value="va" v-model="loans[sel].type">
  75. <label>VA</label>
  76. <input type="radio" name="loan_type" value="usda" v-model="loans[sel].type">
  77. <label>USDA</label>
  78. </section>
  79. <section class="form inputs">
  80. <h3>Loan Details</h3>
  81. <label>Loan Term (years)</label>
  82. <input :value="loans[sel].term"
  83. @input="(e) => loans[sel].term = strip(e)">
  84. <label>Loan Program</label>
  85. <select id="" name="" v-model="loans[sel].program">
  86. <option value="none">None</option>
  87. </select>
  88. <label>Loan to Value (%)</label>
  89. <input :value="loans[sel].ltv" @input="setLtv">
  90. <label>Loan Amount ($)</label>
  91. <input :value="loans[sel].amount"
  92. @input="setAmount">
  93. <label>Housing Expense DTI (%) - Optional</label>
  94. <input :value="loans[sel].housingDti" @input="setHousingDti">
  95. <label>Total DTI (%) - Optional</label>
  96. <input :value="loans[sel].dti" @input="setDti">
  97. <label>Home Owner's Association ($/month)</label>
  98. <input :value="loans[sel].hoa"
  99. @input="(e) => { loans[sel].hoa = strip(e) }">
  100. <label>Interest Rate (%)</label>
  101. <input :value="loans[sel].interest"
  102. @input="(e) => { loans[sel].interest = stripPerc(e) }">
  103. <label>Days of Interest</label>
  104. <input :value="loans[sel].interestDays"
  105. @input="(e) => {loans[sel].interestDays = stripInt(e)}">
  106. <label>Hazard Insurance Escrow (months)</label>
  107. <input :value="loans[sel].hazardEscrow"
  108. @input="(e) => {loans[sel].hazardEscrow = stripInt(e)}">
  109. <label>Hazard Insurance ($/month)</label>
  110. <input :value="loans[sel].hazard"
  111. @input="(e) => {loans[sel].hazard = strip(e)}">
  112. <label>Real Estate Tax Escrow (months)</label>
  113. <input :value="loans[sel].taxEscrow"
  114. @input="e => {loans[sel].taxEscrow = stripInt(e)}">
  115. <label>Real Estate Tax ($/month)</label>
  116. <input :value="loans[sel].tax"
  117. @input="(e) => {loans[sel].tax = strip(e)}">
  118. </section>
  119. <section class="form inputs">
  120. <h3>Fees</h3>
  121. <div v-for="(fee, indx) in estimate.loans[sel].fees"
  122. :key="fee.name + indx" class="fee"
  123. >
  124. <label>
  125. ${{fee.amount}} - {{fee.name}}<br>
  126. {{fee.type}}
  127. </label>
  128. <img width="21" height="21" src="/assets/image/icon/x-red.svg" alt=""
  129. @click="() => estimate.loans[sel].fees.splice(indx, 1)">
  130. </div>
  131. </section>
  132. <section class="form radios">
  133. <h3>Mortgage Insurance</h3>
  134. <input type="radio" name="transaction_type" value="transaction"
  135. @input="e => estimate.transaction = 0"
  136. selected="estimate.transaction == 0">
  137. <label>1.43% - National MI</label>
  138. <input type="radio" name="transaction_type" value="refinance"
  139. @input="e => estimate.transaction = 1"
  140. selected="estimate.transaction == 1">
  141. <label>0.73% - MGIC</label>
  142. </section>
  143. <section class="form inputs">
  144. <button>Generate</button>
  145. </section>
  146. </div>
  147. </template>
  148. <script>
  149. const fees = [
  150. { name: 'Processing fee', type: 'Lender Fees', amount: 500 },
  151. { name: 'Underwriting fee', type: 'Lender Fees', amount: 500 },
  152. { name: 'Credit Report', type: 'Services Required by Lender',
  153. amount: 52.50 },
  154. { name: 'Appraisal', type: 'Services Required by Lender', amount: 52.50 },
  155. { name: 'Title Services', type: 'Title Company', amount: 1000 },
  156. { name: 'Lender\'s Title Insurance', type: 'Title Company', amount: 1599 },
  157. { name: 'Owner\'s Title Insurance', type: 'Title Company', amount: 451.00 },
  158. { name: 'Recording Charges', type: 'Government', amount: 99.00 },
  159. { name: 'State Tax', type: 'Government', amount: 2411.00 },
  160. ]
  161. const example = {
  162. title: "Example",
  163. type: "",
  164. term: 0,
  165. ltv: 0, // Loan to home value ratio
  166. dti: 0,
  167. housingDti: 0,
  168. amount: 0,
  169. interest: 0,
  170. interestDays: 0,
  171. hazard: 0, // Hazard insurance monthly payment
  172. hazardEscrow: 0, // Hazard insurance escrow in months (0 is none)
  173. tax: 0, // Real Estate taxes monthly payment
  174. taxEscrow: 0, // Months to escrow (0 is none)
  175. hoa: 0, // Home owner's association monthly fee
  176. program: "",
  177. pud: true, // Property under development
  178. zip: '',
  179. fees: fees,
  180. }
  181. const loans = [
  182. Object.assign({}, example, {fees: createFees()}),
  183. Object.assign(
  184. Object.assign({}, example),
  185. {title: "Another One", fees: createFees()}
  186. ),
  187. ]
  188. const estimate = {
  189. property: "",
  190. transaction: 0,
  191. price: 0,
  192. borrowers: 0,
  193. creditScore: 0,
  194. mIncome: 0,
  195. loans: loans,
  196. }
  197. // Clone loan from initial example as a new loan
  198. function create() {
  199. this.estimate.loans.push(
  200. Object.assign({}, loans[0], {fees: createFees()})
  201. )
  202. }
  203. function createFees() {
  204. return fees.map(f => Object.assign({}, f))
  205. }
  206. // Strips non-digits from an input box event and returns it's rounded integer.
  207. // It also preserves current valid entry (.)
  208. function strip(e) {
  209. let valid = e.target.value.match(/\d+\.?\d?\d?/)?.[0] ?? ""
  210. e.target.value = valid
  211. return Number(valid || 0)
  212. }
  213. function stripInt(e) {
  214. let value = parseInt(e.target.value.replace(/\D/g, '') || 0)
  215. e.target.value = value
  216. return value
  217. }
  218. function stripPerc(e) {
  219. let num = strip(e)
  220. if (num > 100) {
  221. num = 100
  222. e.target.value = num
  223. }
  224. if (num < 0) {
  225. num = 0
  226. e.target.value = num
  227. }
  228. return num
  229. }
  230. function stripLetters(e) {
  231. let value = (e.target.value.replace(/[^\w\s]/g, '').slice(0, 20) || '')
  232. e.target.value = value
  233. return value
  234. }
  235. function del() {
  236. if (this.loans.length > 1) {
  237. let x = this.sel
  238. this.sel = 0
  239. this.loans.splice(x, 1)
  240. }
  241. }
  242. // Changes loan.ltv's <input> and data() values, then syncs with data.amount
  243. function setLtv(e) {
  244. let ltv = strip(e)
  245. let loan = this.loans[this.sel]
  246. if (!this.estimate.price) return
  247. if (ltv > 100) ltv = 100
  248. if (ltv < 0) ltv = 0
  249. loan.ltv = ltv
  250. loan.amount = (ltv / 100 * this.estimate.price).toFixed(2)
  251. }
  252. // Changes loan.amount's <input> and data() values, then syncs with data.ltv
  253. function setAmount(e) {
  254. let amount = strip(e)
  255. let loan = this.loans[this.sel]
  256. if (!this.estimate.price) return
  257. if (amount > loan.price) amount = loan.price
  258. if (amount < 0) amount = 0
  259. loan.amount = amount
  260. loan.ltv = (amount / this.estimate.price * 100).toFixed(2)
  261. }
  262. // Updates the property price for all loans
  263. function setPrice(e) {
  264. let value = strip(e)
  265. this.estimate.price = value
  266. }
  267. function setDti(e) {
  268. let dti = strip(e)
  269. let loan = this.loans[this.sel]
  270. if (!loan.price) return
  271. if (dti > 100) dti = 100
  272. if (dti < 0) dti = 0
  273. e.target.value = dti
  274. loan.dti = dti
  275. }
  276. function setHousingDti(e) {
  277. let housingDti = strip(e)
  278. let loan = this.loans[this.sel]
  279. if (!loan.price) return
  280. if (housingDti > 100) housingDti = 100
  281. if (housingDti < 0) housingDti = 0
  282. e.target.value = housingDti
  283. loan.housingDti = housingDti
  284. }
  285. export default {
  286. methods: {
  287. setPrice, setLtv, setAmount, setDti, setHousingDti, strip, stripInt,
  288. stripLetters, stripPerc, del, create,
  289. },
  290. props: ['user'],
  291. data() {
  292. return {
  293. estimate: estimate,
  294. loans: estimate.loans,
  295. sel: 0,
  296. fees: loans.fees
  297. }
  298. },
  299. }
  300. </script>