Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

315 linhas
8.1 KiB

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