Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

197 行
4.7 KiB

  1. <template>
  2. <div class="page">
  3. <h2>Estimates</h2>
  4. <section class="form inputs">
  5. <h3>Default Fees</h3>
  6. <div
  7. v-for="(fee, indx) in fees"
  8. :key="fee.name + indx" class="fee"
  9. >
  10. <label @click="() => edit = fee">
  11. ${{fee.amount/100}}{{ fee.perc ? ` ${fee.perc}%` : ''}} - {{fee.name}}<br>
  12. {{fee.type}}
  13. </label>
  14. <img width="21" height="21" src="/assets/image/icon/x-red.svg"
  15. @click="() => remove(fee)">
  16. </div>
  17. <button @click="() => edit = {}">New Fee</button>
  18. </section>
  19. <fee-dialog v-if="edit"
  20. :heading="'Fee'"
  21. :initial="edit"
  22. :price="0"
  23. @close="() => edit = null"
  24. @save="newFee"
  25. />
  26. <section class="inputs estimates">
  27. <h3>Saved Estimates</h3>
  28. <div class="entry" v-for="e in estimates" v-if="!estimate"
  29. @click="() => estimate = e" key="e.id">
  30. <span>
  31. {{e.id}} - {{e.property}} - ${{(e.price/100).toLocaleString()}}
  32. </span>
  33. </div>
  34. <div class="details" v-if="estimate">
  35. <label>
  36. #{{estimate.id}} -
  37. {{estimate.transaction}} -
  38. {{estimate.property}} -
  39. ${{(estimate.price / 100).toLocaleString()}}
  40. </label>
  41. <label>Borrowers: {{estimate.borrower.num}}</label>
  42. <label>Credit: {{estimate.borrower.credit}}</label>
  43. <label>Income: {{(estimate.borrower.income/100).toLocaleString()}}</label>
  44. <div v-for="l in estimate.loans" class="details">
  45. <h4>{{l.title}}</h4>
  46. <label>{{l.type.name}}</label>
  47. <label>Total monthly: ${{format(l.result.totalMonthly)}}</label>
  48. <label>Cash to close: ${{format(l.result.cashToClose)}}</label>
  49. </div>
  50. <button @click="() => download(estimate)">Generate PDF</button>
  51. <button @click="() => deleting = true">Delete</button>
  52. <button @click="() => estimate = null">Cancel</button>
  53. </div>
  54. </section>
  55. <DDialog v-if="dlink" @close="() => dlink = ''"
  56. :fileName="`estimate-${estimate.id}.pdf`" :url="dlink">
  57. </DDialog>
  58. <Dialog v-if="deleting" @close="() => deleting = false">
  59. <h3>Are you sure you want to delete this estimate?</h3>
  60. <button @click="() => del(estimate)">Confirm</button>
  61. </Dialog>
  62. </div>
  63. </template>
  64. <script setup>
  65. import { ref, computed, onMounted } from 'vue'
  66. import FeeDialog from "./fee-dialog.vue"
  67. import DDialog from "./download-dialog.vue"
  68. import Dialog from "./dialog.vue"
  69. import { format } from "../helpers.js"
  70. const props = defineProps(['user', 'fees', 'token'])
  71. const emit = defineEmits(['addFeeTemp', 'removeFeeTemp', 'preview'])
  72. let edit = ref(null)
  73. let estimates = ref([])
  74. let estimate = ref()
  75. let dlink = ref("")
  76. let deleting = ref()
  77. function newFee(fee, isDebit) {
  78. if (!isDebit) {
  79. fee.amount = -1 * fee.amount || 0
  80. fee.perc = -1 * fee.perc || 0
  81. }
  82. fetch(`/api/fee`,
  83. {method: 'POST',
  84. body: JSON.stringify(fee),
  85. headers: {
  86. "Accept": "application/json",
  87. "Authorization": `Bearer ${props.token}`,
  88. },
  89. }).then(resp => {
  90. if (resp.ok && resp.status == 200) {
  91. resp.json().then(r => emit('addFeeTemp', r))
  92. } else {
  93. // resp.text().then(t => this.errors = [t])
  94. // window.location.hash = 'new'
  95. resp.text().then(t => console.log(t))
  96. }
  97. })
  98. edit.value = null
  99. }
  100. function newType() {
  101. }
  102. function remove(fee) {
  103. fetch(`/api/fee`,
  104. {method: 'DELETE',
  105. headers: {
  106. "Accept": "application/json",
  107. "Authorization": `Bearer ${props.token}`,
  108. },
  109. body: JSON.stringify(fee)
  110. }).then(response => {
  111. if (response.ok) { emit('removeFeeTemp', fee) } else {
  112. response.text().then(t => console.log(t))
  113. }
  114. })
  115. }
  116. function getEstimates() {
  117. fetch(`/api/estimates`,
  118. {method: 'GET',
  119. headers: {
  120. "Accept": "application/json",
  121. "Authorization": `Bearer ${props.token}`,
  122. },
  123. }).then(response => {
  124. if (response.ok) { return response.json() } else {
  125. response.text().then(t => console.log(t))
  126. }
  127. }).then (result => {
  128. if (!result || !result.length) return // Exit if token is invalid or no fees are saved
  129. estimates.value = result
  130. // console.log(result)
  131. })
  132. }
  133. function del(e) {
  134. fetch(`/api/estimate`,
  135. {method: 'DELETE',
  136. body: JSON.stringify(e),
  137. headers: {
  138. "Accept": "application/json",
  139. "Authorization": `Bearer ${props.token}`,
  140. },
  141. }).then(response => {
  142. if (response.ok) {
  143. estimates.value = estimates.value.filter(e => e.id != estimate.value.id)
  144. estimate.value = null
  145. deleting.value = false
  146. }
  147. })
  148. }
  149. function download(estimate) {
  150. fetch(`/api/pdf`,
  151. {method: 'POST',
  152. body: JSON.stringify(estimate),
  153. headers: {
  154. "Accept": "application/json",
  155. "Authorization": `Bearer ${props.token}`,
  156. },
  157. }).then(response => {
  158. if (response.ok) { return response.blob() }
  159. }).then (result => {
  160. if (!result) return // Exit if token is invalid or blank file returned
  161. dlink.value = URL.createObjectURL(result)
  162. })
  163. }
  164. onMounted(() => {
  165. getEstimates()
  166. })
  167. </script>
  168. <style scoped>
  169. .modal a.button {
  170. margin: auto;
  171. margin-top: 30px;
  172. }
  173. </style>