Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

279 lines
7.3 KiB

  1. <template>
  2. <section class="form inputs">
  3. <h3>Loan</h3>
  4. <label>Name</label>
  5. <input :value="loans[sel].title" required
  6. @input="(e) => $emit('update:name', stripLetters(e))">
  7. <button @click="() => $emit('del')">Delete</button>
  8. </section>
  9. <section class="form inputs">
  10. <div class="hint">
  11. <img class="icon" src="/assets/image/icon/question-circle.svg" alt="">
  12. <div class="tooltip">
  13. <p>Assumes borrower is not self employed, not bankrupt in the past 7
  14. years, a citizen, and intends to occupy the property.</p>
  15. </div>
  16. </div>
  17. <h3>Borrower</h3>
  18. <label>Number of Borrowers</label>
  19. <input :value="estimate.borrowers"
  20. @input="(e) => $emit('update:borrowers', stripInt(e))">
  21. <label>Credit Score</label>
  22. <input :value="estimate.creditScore"
  23. @input="(e) => $emit('update:creditScore', stripInt(e))">
  24. <label>Monthly Income ($)</label>
  25. <input :value="estimate.mIncome"
  26. @input="(e) => $emit('update:mIncome', strip(e))">
  27. </section>
  28. <section class="radios form">
  29. <h3>Transaction Type</h3>
  30. <input selected type="radio" name="transaction_type" value="0"
  31. :value="estimate.transaction"
  32. @input="() => $emit('update:transaction', 0)"
  33. >
  34. <label>Purchase</label>
  35. <input type="radio" name="transaction_type" value="1"
  36. :value="estimate.transaction"
  37. @input="() => $emit('update:transaction', 1)"
  38. >
  39. <label>Refinance</label>
  40. </section>
  41. <section class="form inputs">
  42. <h3>Property Details</h3>
  43. <label>Price ($)</label>
  44. <input :value="estimate.price" @input="(e) => $emit('update:price', strip(e))">
  45. <label>Type</label>
  46. <select id="" name=""
  47. :value="estimate.property"
  48. @change="(e) => $emit('update:property', e.target.value)">
  49. <option value="attched">Single Family Attached</option>
  50. <option value="detached">Single Family Detached</option>
  51. <option value="lorise">Lo-rise (4 stories or less)</option>
  52. <option value="hirise">Hi-rise (over 4 stories)</option>
  53. </select>
  54. </section>
  55. <section class="radios form">
  56. <h3>Loan Type</h3>
  57. <input type="radio"
  58. name="loan_type"
  59. value="conv"
  60. @change="(e) => $emit('update:loanType', e.target.value)"
  61. >
  62. <label>Conventional</label>
  63. <input type="radio"
  64. name="loan_type"
  65. value="fha"
  66. :checked="loans[sel].type == 'fha'"
  67. @change="(e) => $emit('update:loanType', e.target.value)">
  68. <label>FHA</label>
  69. <input type="radio"
  70. name="loan_type"
  71. value="va"
  72. :checked="loans[sel].type == 'va'"
  73. @change="(e) => $emit('update:loanType', e.target.value)">
  74. <label>VA</label>
  75. <input type="radio"
  76. name="loan_type"
  77. value="usda"
  78. :checked="loans[sel].type == 'usda'"
  79. @change="(e) => $emit('update:loanType', e.target.value)">
  80. <label>USDA</label>
  81. </section>
  82. <section class="form inputs">
  83. <h3>Loan Details</h3>
  84. <label>Loan Term (years)</label>
  85. <input :value="loans[sel].term"
  86. @input="(e) => $emit('update:term', stripInt(e))">
  87. <label>Loan Program</label>
  88. <select id="" name=""
  89. :value="loans[sel].program"
  90. @change="(e) => $emit('update:program', e.target.value)">
  91. <option value="none">None</option>
  92. </select>
  93. <label>Loan to Value (%)</label>
  94. <input :value="loans[sel].ltv" @input="(e) => $emit('update:ltv', e)">
  95. <label>Loan Amount ($)</label>
  96. <input :value="loans[sel].amount"
  97. @input="(e) => $emit('update:amount', e)">
  98. <label>Housing Expense DTI (%) - Optional</label>
  99. <input :value="loans[sel].housingDti"
  100. @input="(e) => $emit('update:houstingDti', e)">
  101. <label>Total DTI (%) - Optional</label>
  102. <input :value="loans[sel].dti" @input="(e) => $emit('update:dti', strip(e))">
  103. <label>Home Owner's Association ($/month)</label>
  104. <input :value="loans[sel].hoa"
  105. @input="(e) => { $emit('update:hoa', strip(e)) }">
  106. <label>Interest Rate (%)</label>
  107. <input :value="loans[sel].interest"
  108. @input="(e) => { $emit('update:interest', stripPerc(e)) }">
  109. <label>Days of Interest</label>
  110. <input :value="loans[sel].interestDays"
  111. @input="(e) => $emit('update:interestDays', stripInt(e))">
  112. <label>Hazard Insurance Escrow (months)</label>
  113. <input :value="loans[sel].hazardEscrow"
  114. @input="(e) => { $emit('update:hazardEscrow', stripInt(e)) }">
  115. <label>Hazard Insurance ($/month)</label>
  116. <input :value="loans[sel].hazard"
  117. @input="(e) => $emit('update:hazard', strip(e))">
  118. <label>Real Estate Tax Escrow (months)</label>
  119. <input :value="loans[sel].taxEscrow"
  120. @input="e => {loans[sel].taxEscrow = stripInt(e)}">
  121. <label>Real Estate Tax ($/month)</label>
  122. <input :value="loans[sel].tax"
  123. @input="(e) => $emit('update:tax', strip(e))">
  124. </section>
  125. <section class="form inputs">
  126. <h3>Fees</h3>
  127. <div v-for="(fee, indx) in estimate.loans[sel].fees"
  128. :key="fee.name + indx" class="fee"
  129. >
  130. <label>
  131. ${{fee.amount}}{{ fee.perc ? ` ${fee.perc}%` : ''}} - {{fee.name}}<br>
  132. {{fee.type}}
  133. </label>
  134. <img width="21" height="21" src="/assets/image/icon/x-red.svg"
  135. @click="() => estimate.loans[sel].fees.splice(indx, 1)">
  136. </div>
  137. <button @click="resetFees">Reset</button>
  138. <button @click="createFee">New</button>
  139. </section>
  140. <fee-dialog v-if="newFee"
  141. :heading="'New Fee'"
  142. :initial="{}"
  143. :price="estimate.price"
  144. @close="() => newFee = null"
  145. @save="addFee"
  146. />
  147. <section class="form radios">
  148. <h3>Mortgage Insurance</h3>
  149. </section>
  150. <section class="form inputs">
  151. <button @click="generate">Generate</button>
  152. <ul class="errors">
  153. <li v-for="e in errors">{{e}}</li>
  154. </ul>
  155. </section>
  156. </template>
  157. <script>
  158. import FeeDialog from "../fee-dialog.vue"
  159. import { stripLetters, strip, stripInt, stripPerc } from "../../helpers.js"
  160. const newFee = {
  161. name: '', type: '', amount: 0, perc: 0
  162. }
  163. // Setup this.newFee for the creation dialog
  164. function createFee() {
  165. this.newFee = Object.assign({}, newFee)
  166. }
  167. // If valid, add the current this.newFee to the array of fees and reset
  168. // this.newFee to null
  169. function addFee(fee, isDebit) {
  170. if (!isDebit) fee.amount = fee.amount * -1
  171. this.estimate.loans[this.sel].fees.push(fee)
  172. this.newFee = null
  173. }
  174. function validate() {
  175. let errors = []
  176. const estimate = this.estimate
  177. // Alternative attribute names for error messages
  178. const names = {
  179. term: "loan term",
  180. ltv: "loan to value",
  181. hazard: "hazard insurance",
  182. hazardEscrow: "hazard insurance escrow",
  183. }
  184. if (!estimate.property) {
  185. errors.push("Missing property type.")
  186. } else if (!estimate.price) {
  187. errors.push("Missing property price.")
  188. } else if (!estimate.borrowers) {
  189. errors.push("Missing number of borrowers.")
  190. } else if (!estimate.creditScore) {
  191. errors.push("Missing credit score.")
  192. } else if (!estimate.mIncome) {
  193. errors.push("Missing monthly income.")
  194. }
  195. estimate.loans.forEach(l => {
  196. if (errors.length) return
  197. if (!l.amount) {
  198. errors.push("Loan amount cannot be zero")
  199. } else if (!l.interest) {
  200. errors.push("Interest rate cannot be zero")
  201. } else if (!l.term) {
  202. errors.push("Loan term cannot be zero")
  203. }
  204. })
  205. }
  206. export default {
  207. components: { FeeDialog },
  208. methods: {
  209. strip, stripInt, stripLetters, stripPerc, createFee, addFee, validate
  210. },
  211. props: ['estimate', 'loans', 'sel'],
  212. // Loan updates assume the currently selected loan is being modified, and
  213. // $emit has no need to clarify.
  214. emits: [
  215. 'del',
  216. 'update:name',
  217. 'update:borrowers',
  218. 'update:creditScore',
  219. 'update:mIncome',
  220. 'update:transaction',
  221. 'update:price',
  222. 'update:propertyType',
  223. // Loan specific emits
  224. 'update:loanType',
  225. 'update:term',
  226. 'update:program',
  227. 'update:ltv',
  228. 'update:amount',
  229. 'update:housingDti',
  230. 'update:dti',
  231. 'update:hoa',
  232. ],
  233. data() {
  234. return {
  235. newFee: null,
  236. errors: [],
  237. hash: window.location.hash
  238. }
  239. },
  240. created() {
  241. }
  242. }
  243. </script>