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

293 строки
7.4 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>Templates</h3>
  28. <div class="entry" v-for="t in templates" v-if="!template"
  29. @click="() => template = t" key="t.id">
  30. <span>
  31. {{t.estimate.id}} - {{t.estimate.property}} - ${{(t.estimate.price/100).toLocaleString()}}
  32. </span>
  33. </div>
  34. <div class="details" v-if="template">
  35. <label>
  36. #{{template.estimate.id}} -
  37. {{template.estimate.transaction}} -
  38. {{template.estimate.property}} -
  39. ${{(template.estimate.price / 100).toLocaleString()}}
  40. </label>
  41. <label>Borrowers: {{template.estimate.borrower.num}}</label>
  42. <label>Credit: {{template.estimate.borrower.credit}}</label>
  43. <label>Income: {{(template.estimate.borrower.income/100).toLocaleString()}}</label>
  44. <div v-for="l in template.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(template.estimate)">Generate PDF</button>
  51. <button @click="() => deletingt = true">Remove</button>
  52. <button @click="() => template = null">Cancel</button>
  53. </div>
  54. </section>
  55. <section class="inputs estimates">
  56. <h3>Saved Estimates</h3>
  57. <div class="entry" v-for="e in estimates" v-if="!estimate"
  58. @click="() => estimate = e" key="e.id">
  59. <span>
  60. {{e.id}} - {{e.property}} - ${{(e.price/100).toLocaleString()}}
  61. </span>
  62. </div>
  63. <div class="details" v-if="estimate">
  64. <label>
  65. #{{estimate.id}} -
  66. {{estimate.transaction}} -
  67. {{estimate.property}} -
  68. ${{(estimate.price / 100).toLocaleString()}}
  69. </label>
  70. <label>Borrowers: {{estimate.borrower.num}}</label>
  71. <label>Credit: {{estimate.borrower.credit}}</label>
  72. <label>Income: {{(estimate.borrower.income/100).toLocaleString()}}</label>
  73. <div v-for="l in estimate.loans" class="details">
  74. <h4>{{l.title}}</h4>
  75. <label>{{l.type.name}}</label>
  76. <label>Total monthly: ${{format(l.result.totalMonthly)}}</label>
  77. <label>Cash to close: ${{format(l.result.cashToClose)}}</label>
  78. </div>
  79. <button @click="() => download(estimate)">Generate PDF</button>
  80. <button @click="() => saveTemplate(estimate)">Save As Template</button>
  81. <button @click="() => deleting = true" :disabled="isTemplate()">Delete</button>
  82. <button @click="() => estimate = null">Cancel</button>
  83. </div>
  84. </section>
  85. <DDialog v-if="dlink" @close="() => dlink = ''"
  86. :fileName="`estimate-${estimate.id}.pdf`" :url="dlink">
  87. </DDialog>
  88. <Dialog v-if="deleting" @close="() => deleting = false">
  89. <h3>Are you sure you want to delete this estimate?</h3>
  90. <button @click="() => del(estimate)">Confirm</button>
  91. </Dialog>
  92. <Dialog v-if="deletingt" @close="() => deletingt = false">
  93. <h3>Are you sure you want to delete this template?</h3>
  94. <button @click="() => delt(template)">Confirm</button>
  95. </Dialog>
  96. </div>
  97. </template>
  98. <script setup>
  99. import { ref, computed, onMounted } from 'vue'
  100. import FeeDialog from "./fee-dialog.vue"
  101. import DDialog from "./download-dialog.vue"
  102. import Dialog from "./dialog.vue"
  103. import { format } from "../helpers.js"
  104. const props = defineProps(['user', 'fees', 'token'])
  105. const emit = defineEmits(['addFeeTemp', 'removeFeeTemp', 'preview'])
  106. let edit = ref(null)
  107. let estimates = ref([])
  108. let templates = ref([])
  109. let estimate = ref()
  110. let template = ref()
  111. let dlink = ref("")
  112. let deleting = ref(false)
  113. let deletingt = ref(false)
  114. function newFee(fee, isDebit) {
  115. if (!isDebit) {
  116. fee.amount = -1 * fee.amount || 0
  117. fee.perc = -1 * fee.perc || 0
  118. }
  119. fetch(`/api/fee`,
  120. {method: 'POST',
  121. body: JSON.stringify(fee),
  122. headers: {
  123. "Accept": "application/json",
  124. "Authorization": `Bearer ${props.token}`,
  125. },
  126. }).then(resp => {
  127. if (resp.ok && resp.status == 200) {
  128. resp.json().then(r => emit('addFeeTemp', r))
  129. } else {
  130. // resp.text().then(t => this.errors = [t])
  131. // window.location.hash = 'new'
  132. resp.text().then(t => console.log(t))
  133. }
  134. })
  135. edit.value = null
  136. }
  137. function newType() {
  138. }
  139. function remove(fee) {
  140. fetch(`/api/fee`,
  141. {method: 'DELETE',
  142. headers: {
  143. "Accept": "application/json",
  144. "Authorization": `Bearer ${props.token}`,
  145. },
  146. body: JSON.stringify(fee)
  147. }).then(response => {
  148. if (response.ok) { emit('removeFeeTemp', fee) } else {
  149. response.text().then(t => console.log(t))
  150. }
  151. })
  152. }
  153. function getEstimates() {
  154. fetch(`/api/estimates`,
  155. {method: 'GET',
  156. headers: {
  157. "Accept": "application/json",
  158. "Authorization": `Bearer ${props.token}`,
  159. },
  160. }).then(response => {
  161. if (response.ok) { return response.json() } else {
  162. response.text().then(t => console.log(t))
  163. }
  164. }).then (result => {
  165. if (!result || !result.length) return // Exit if token is invalid or no fees are saved
  166. estimates.value = result
  167. })
  168. }
  169. function getTemplates() {
  170. fetch(`/api/templates`,
  171. {method: 'GET',
  172. headers: {
  173. "Accept": "application/json",
  174. "Authorization": `Bearer ${props.token}`,
  175. },
  176. }).then(response => {
  177. if (response.ok) { return response.json() } else {
  178. response.text().then(t => console.log(t))
  179. }
  180. }).then (result => {
  181. if (!result || !result.length) return // Exit if token is invalid or no fees are saved
  182. templates.value = result
  183. })
  184. }
  185. function saveTemplate(e) {
  186. fetch(`/api/templates`,
  187. {method: 'POST',
  188. body: JSON.stringify(e),
  189. headers: {
  190. "Accept": "application/json",
  191. "Authorization": `Bearer ${props.token}`,
  192. },
  193. }).then(response => {
  194. if (response.ok) { getTemplates() }
  195. })
  196. }
  197. function del(e) {
  198. fetch(`/api/estimate`,
  199. {method: 'DELETE',
  200. body: JSON.stringify(e),
  201. headers: {
  202. "Accept": "application/json",
  203. "Authorization": `Bearer ${props.token}`,
  204. },
  205. }).then(response => {
  206. if (response.ok) {
  207. estimates.value = estimates.value.filter(e => e.id != estimate.value.id)
  208. estimate.value = null
  209. deleting.value = false
  210. }
  211. })
  212. }
  213. function delt(t) {
  214. fetch(`/api/templates`,
  215. {method: 'DELETE',
  216. body: JSON.stringify(t),
  217. headers: {
  218. "Accept": "application/json",
  219. "Authorization": `Bearer ${props.token}`,
  220. },
  221. }).then(response => {
  222. if (response.ok) {
  223. templates.value = templates.value.filter(t => t.id != template.value.id)
  224. template.value = null
  225. deletingt.value = false
  226. }
  227. })
  228. }
  229. function download(estimate) {
  230. fetch(`/api/pdf`,
  231. {method: 'POST',
  232. body: JSON.stringify(estimate),
  233. headers: {
  234. "Accept": "application/json",
  235. "Authorization": `Bearer ${props.token}`,
  236. },
  237. }).then(response => {
  238. if (response.ok) { return response.blob() }
  239. }).then (result => {
  240. if (!result) return // Exit if token is invalid or blank file returned
  241. dlink.value = URL.createObjectURL(result)
  242. })
  243. }
  244. function isTemplate() {
  245. return templates.value.some( t => t.estimate.id == estimate.value.id )
  246. }
  247. onMounted(() => {
  248. getEstimates()
  249. getTemplates()
  250. })
  251. </script>
  252. <style scoped>
  253. .modal a.button {
  254. margin: auto;
  255. margin-top: 30px;
  256. }
  257. </style>