Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 
 

317 строки
8.2 KiB

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