ProfilePaymentsDialog.vue 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. <template>
  2. <q-dialog ref="dialogRef" persistent maximized transition-show="slide-left" transition-hide="slide-right">
  3. <div class="bg-page full-height column no-shadow">
  4. <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile bg-surface">
  5. <q-btn v-close-popup icon="mdi-chevron-left" flat round dense color="primary" />
  6. <q-space />
  7. <span class="text-subtitle1 text-primary font16 fontbold gradient-diarista">{{ $t('profile.payments.subtitle') }}</span>
  8. <q-space />
  9. </div>
  10. <div v-if="loading" class="col flex flex-center">
  11. <q-spinner color="primary" size="3em" />
  12. </div>
  13. <div v-else class="col column q-px-md q-pt-lg overflow-auto">
  14. <span class="text-primary q-mb-lg ml-xs font16 fontbold gradient-diarista">{{ $t('profile.payments.description')
  15. }}</span>
  16. <div class="payment-card-container q-pa-lg font12 fontregular">
  17. <div>
  18. <div v-if="paymentMethods.length === 0" class="column items-center justify-center q-py-xl">
  19. <q-icon name="mdi-credit-card-off-outline" size="64px" color="grey-4" class="q-mb-md" />
  20. <p class="text-text text-center q-mb-xs">
  21. {{ $t('profile.payments.no_cards') }}
  22. </p>
  23. <p class="text-grey-6 text-center q-mb-xl">
  24. {{ $t('profile.payments.add_first_card') }}
  25. </p>
  26. </div>
  27. <div v-else class="column q-gutter-y-md">
  28. <div v-for="item in paymentMethods" :key="item.id" class="card-item-box row items-center no-wrap q-pa-md">
  29. <div class="brand-logo-wrapper q-mr-md">
  30. <BrandDetectorPanel :brand="item.brand" />
  31. </div>
  32. <div class="col column">
  33. <span class="card-type text-dark">{{ $t('profile.payments.credit') }}</span>
  34. <div class="row items-center no-wrap">
  35. <span class="card-bank-new">{{ cardLabel(item) }}</span>
  36. </div>
  37. <span class="card-number-new">{{ '**** ' + item.last_four_digits }}</span>
  38. </div>
  39. <div class="row items-end">
  40. <q-btn flat round icon="mdi-pencil-outline" color="grey-8" size="15px" @click="openEditDialog(item)" />
  41. <q-btn flat round icon="mdi-trash-can-outline" color="grey-8" size="15px" @click="confirmRemove(item)" />
  42. </div>
  43. </div>
  44. </div>
  45. </div>
  46. <div class="q-mt-xl">
  47. <q-btn unelevated rounded no-caps color="primary" class="full-width" padding="8px 16px" :label="$t('profile.payments.add_card')" @click="openAddDialog" />
  48. </div>
  49. </div>
  50. </div>
  51. </div>
  52. </q-dialog>
  53. </template>
  54. <script setup>
  55. import { ref, onMounted } from 'vue';
  56. import { useDialogPluginComponent, useQuasar } from 'quasar';
  57. import { userStore } from 'src/stores/user';
  58. import { getClientPaymentMethods, deleteClientPaymentMethod } from 'src/api/clientPaymentMethod';
  59. import ProfilePaymentAddDialog from './ProfilePaymentAddDialog.vue';
  60. import ProfilePaymentRemoveDialog from './ProfilePaymentRemoveDialog.vue';
  61. import BrandDetectorPanel from '../brandDetector/BrandDetectorPanel.vue';
  62. defineEmits([...useDialogPluginComponent.emits]);
  63. const { dialogRef } = useDialogPluginComponent();
  64. const $q = useQuasar();
  65. const store = userStore();
  66. const paymentMethods = ref([]);
  67. const loading = ref(false);
  68. const cardLabel = (item) => {
  69. const parts = [];
  70. if (item.card_name) parts.push(item.card_name);
  71. if (item.brand) parts.push(item.brand.charAt(0).toUpperCase() + item.brand.slice(1));
  72. return parts.join(' - ') || '—';
  73. };
  74. const openAddDialog = () => {
  75. $q.dialog({
  76. component: ProfilePaymentAddDialog,
  77. componentProps: {
  78. clientId: store.user?.client_id,
  79. },
  80. }).onOk(() => {
  81. loadPaymentMethods();
  82. });
  83. };
  84. const openEditDialog = (item) => {
  85. $q.dialog({
  86. component: ProfilePaymentAddDialog,
  87. componentProps: {
  88. clientId: store.user?.client_id,
  89. paymentMethod: item,
  90. },
  91. }).onOk(() => {
  92. loadPaymentMethods();
  93. });
  94. };
  95. const confirmRemove = (item) => {
  96. $q.dialog({
  97. component: ProfilePaymentRemoveDialog,
  98. }).onOk(async () => {
  99. try {
  100. await deleteClientPaymentMethod(item.id);
  101. paymentMethods.value = paymentMethods.value.filter(p => p.id !== item.id);
  102. } catch (error) {
  103. console.error('Erro ao remover cartão:', error);
  104. }
  105. });
  106. };
  107. const loadPaymentMethods = async () => {
  108. loading.value = true;
  109. try {
  110. const clientId = store.user?.client_id;
  111. if (clientId) {
  112. paymentMethods.value = await getClientPaymentMethods(clientId);
  113. }
  114. } catch (error) {
  115. console.error('Erro ao carregar métodos de pagamento:', error);
  116. } finally {
  117. loading.value = false;
  118. }
  119. };
  120. onMounted(loadPaymentMethods);
  121. </script>
  122. <style scoped lang="scss">
  123. .payment-card-container {
  124. background: white;
  125. border-radius: 30px;
  126. box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
  127. margin-bottom: 40px;
  128. }
  129. .card-item-box {
  130. border: 1px solid #b5b5b5;
  131. border-radius: 4px;
  132. background: white;
  133. }
  134. .brand-logo-wrapper {
  135. width: 50px;
  136. height: 32px;
  137. display: flex;
  138. align-items: center;
  139. justify-content: center;
  140. flex-shrink: 0;
  141. }
  142. .card-type {
  143. line-height: 1.2;
  144. }
  145. .card-bank-new {
  146. color: #666;
  147. line-height: 1.4;
  148. }
  149. .card-number-new {
  150. color: #333;
  151. letter-spacing: 0.5px;
  152. line-height: 1.2;
  153. }
  154. .ml-xs {
  155. margin-left: 4px;
  156. }
  157. </style>