CarteirinhaPage.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. <template>
  2. <div class="carteirinha-page">
  3. <DefaultHeaderPage class="q-px-sm"/>
  4. <div class="card-container">
  5. <q-card
  6. flat
  7. class="associado-card"
  8. :class="isVertical ? 'card-vertical' : 'card-horizontal'"
  9. >
  10. <div class="card-logo-row text-center">
  11. <img :src="LogoSerPratiParceiro" class="card-logo" alt="SerPrati" />
  12. </div>
  13. <div v-if="isOnLeave" class="on-leave-banner">
  14. <q-icon name="mdi-account-off-outline" size="16px" class="q-mr-xs" />
  15. {{ $t('associado.on_leave_carteirinha') }}
  16. </div>
  17. <div class="card-body" :class="isVertical ? 'card-body--vertical' : 'card-body--horizontal'">
  18. <q-avatar
  19. class="card-avatar"
  20. :size="isVertical ? '100px' : '76px'"
  21. >
  22. <img v-if="user?.photo_url" :src="user.photo_url" />
  23. <q-icon v-else name="mdi-account" color="white" :size="isVertical ? '64px' : '40px'" />
  24. </q-avatar>
  25. <template v-if="!isVertical">
  26. <div class="card-info">
  27. <div class="card-info__row">
  28. <span class="card-info__label">{{ $t('common.terms.name') }}</span>
  29. <span class="card-info__value">{{ user?.name ?? '—' }}</span>
  30. </div>
  31. <div class="card-info__row q-pt-sm">
  32. <span class="card-info__label">{{ $t('common.terms.cpf') }}</span>
  33. <span class="card-info__value">{{ user?.cpf ?? '—' }}</span>
  34. </div>
  35. <div class="card-info__row q-pt-sm">
  36. <span class="card-info__label">{{ $t('associado.registration') }}</span>
  37. <span class="card-info__value">{{ user?.registration ?? '—' }}</span>
  38. </div>
  39. </div>
  40. <div class="card-qr column">
  41. <canvas v-if="qrReady" ref="qrCanvas" />
  42. <q-spinner v-else color="white" size="48px" />
  43. <div v-if="user?.expiry_date" class="card-info__row q-pt-sm text-right">
  44. <span class="card-info__label text-right">{{ $t('associado.validity') }}</span>
  45. <span class="card-info__value">{{ user.expiry_date }}</span>
  46. </div>
  47. </div>
  48. </template>
  49. <div v-if="isVertical" class="card-body__vertical-content">
  50. <div class="card-info card-info--vertical">
  51. <div class="card-info__row">
  52. <span class="card-info__label">{{ $t('common.terms.name') }}</span>
  53. <span class="card-info__value">{{ user?.name ?? '—' }}</span>
  54. </div>
  55. <div class="card-info__row q-pt-xs">
  56. <span class="card-info__label">{{ $t('common.terms.cpf') }}</span>
  57. <span class="card-info__value">{{ user?.cpf ?? '—' }}</span>
  58. </div>
  59. <div class="card-info__row q-pt-xs">
  60. <span class="card-info__label">{{ $t('associado.registration') }}</span>
  61. <span class="card-info__value">{{ user?.registration ?? '—' }}</span>
  62. </div>
  63. <div class="card-info__row q-pt-xs">
  64. <span class="card-info__label">{{ $t('associado.validity') }}</span>
  65. <span class="card-info__value">{{ user?.expiry_date ?? '—' }}</span>
  66. </div>
  67. </div>
  68. <canvas v-if="qrReady" ref="qrCanvas" class="card-qr__canvas" />
  69. <q-spinner v-else color="white" size="48px" />
  70. </div>
  71. </div>
  72. </q-card>
  73. <q-btn
  74. round
  75. flat
  76. :icon="isVertical ? 'mdi-phone-rotate-landscape' : 'mdi-phone-rotate-portrait'"
  77. color="violet-normal"
  78. size="sm"
  79. :title="isVertical ? $t('associado.horizontal') : $t('associado.vertical')"
  80. @click="isVertical = !isVertical"
  81. />
  82. </div>
  83. </div>
  84. </template>
  85. <script setup>
  86. import { ref, computed, onMounted, useTemplateRef, watch, nextTick } from "vue";
  87. import QRCode from "qrcode";
  88. import { userStore } from "src/stores/user";
  89. import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
  90. import LogoSerPratiParceiro from "src/assets/logo_serprati_parceiro.svg";
  91. const store = userStore();
  92. const user = ref(null);
  93. const isOnLeave = computed(() => {
  94. const s = user.value?.status;
  95. return s === "on_leave" || s?.value === "on_leave";
  96. });
  97. const isVertical = ref(false);
  98. const qrCanvas = useTemplateRef("qrCanvas");
  99. const qrReady = ref(false);
  100. const QR_SIZE_HORIZONTAL = 90;
  101. const QR_SIZE_VERTICAL = 110;
  102. const generateQR = async () => {
  103. if (!user.value?.cpf && !user.value?.registration) return;
  104. await nextTick();
  105. const canvas = qrCanvas.value instanceof Array ? qrCanvas.value[0] : qrCanvas.value;
  106. if (!canvas) return;
  107. const qrData = `CPF:${user.value.cpf ?? ""}|MAT:${user.value.registration ?? ""}`;
  108. const size = isVertical.value ? QR_SIZE_VERTICAL : QR_SIZE_HORIZONTAL;
  109. await QRCode.toCanvas(canvas, qrData, { width: size, margin: 1, color: { dark: "#ffffff", light: "#661d75" } });
  110. };
  111. watch(isVertical, async () => {
  112. await nextTick();
  113. await generateQR();
  114. });
  115. onMounted(async () => {
  116. await store.fetchUser();
  117. user.value = store.user;
  118. qrReady.value = true;
  119. await generateQR();
  120. });
  121. </script>
  122. <style lang="scss" scoped>
  123. @use "src/css/quasar.variables.scss" as vars;
  124. .associado-card {
  125. border-radius: 16px;
  126. background: linear-gradient(180deg, #661D75 0%, #BF36DB 100%);
  127. overflow: hidden;
  128. box-shadow: 0 8px 32px rgba(77, 22, 88, 0.35);
  129. color: vars.$neutral-light;
  130. .card-logo-row {
  131. .card-logo {
  132. height: 65px;
  133. }
  134. }
  135. .on-leave-banner {
  136. background: rgba(255, 152, 0, 0.85);
  137. color: #fff;
  138. font-size: 11px;
  139. font-weight: 600;
  140. text-align: center;
  141. padding: 6px 12px;
  142. display: flex;
  143. align-items: center;
  144. justify-content: center;
  145. gap: 4px;
  146. }
  147. .card-body {
  148. padding: 8px 16px 16px;
  149. display: flex;
  150. gap: 12px;
  151. &--horizontal {
  152. flex-direction: row;
  153. align-items: center;
  154. }
  155. &--vertical {
  156. flex-direction: column;
  157. align-items: center;
  158. text-align: center;
  159. }
  160. }
  161. .card-avatar {
  162. border: 2px solid rgba(255, 255, 255, 0.4);
  163. background: rgba(255, 255, 255, 0.15);
  164. flex-shrink: 0;
  165. img {
  166. width: 100%;
  167. height: 100%;
  168. object-fit: cover;
  169. }
  170. }
  171. .card-info {
  172. flex: 1;
  173. display: flex;
  174. flex-direction: column;
  175. gap: 2px;
  176. .card-info__label {
  177. font-size: 11px;
  178. opacity: 0.8;
  179. }
  180. .card-info__value {
  181. font-size: 15px;
  182. font-weight: 600;
  183. }
  184. &--vertical {
  185. width: 100%;
  186. align-items: flex-start;
  187. text-align: left;
  188. }
  189. &__name {
  190. font-size: 14px;
  191. font-weight: 600;
  192. color: vars.$neutral-light;
  193. margin-bottom: 4px;
  194. }
  195. &__row {
  196. display: flex;
  197. flex-direction: column;
  198. line-height: 1.2;
  199. }
  200. &__label {
  201. font-size: 9px;
  202. opacity: 0.75;
  203. text-transform: uppercase;
  204. letter-spacing: 0.4px;
  205. }
  206. &__value {
  207. font-size: 11px;
  208. font-weight: 500;
  209. color: vars.$neutral-light;
  210. }
  211. }
  212. .card-qr {
  213. flex-shrink: 0;
  214. display: flex;
  215. align-items: center;
  216. justify-content: center;
  217. canvas {
  218. border-radius: 6px;
  219. }
  220. }
  221. .card-body__vertical-content {
  222. width: 100%;
  223. display: flex;
  224. flex-direction: row;
  225. align-items: flex-end;
  226. justify-content: space-between;
  227. gap: 8px;
  228. }
  229. .card-qr__canvas {
  230. border-radius: 6px;
  231. flex-shrink: 0;
  232. }
  233. &.card-horizontal {
  234. width: min(360px, 100%);
  235. }
  236. &.card-vertical {
  237. width: min(260px, 100%);
  238. }
  239. }
  240. .card-container {
  241. display: flex;
  242. flex-direction: column;
  243. align-items: center;
  244. gap: 12px;
  245. margin-top: 16px;
  246. overflow: hidden;
  247. }
  248. .carteirinha-page {
  249. background: vars.$violet-light;
  250. min-height: calc(100dvh - 68px);
  251. box-sizing: border-box;
  252. overflow-x: hidden;
  253. }
  254. </style>