| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- <template>
- <div>
- <DefaultHeaderPage />
- <div class="q-pa-md">
- <q-card flat class="profile-card">
- <div class="profile-header row items-center q-pa-lg">
- <div class="profile-avatar-wrap q-mr-md">
- <q-avatar size="76px" class="profile-avatar">
- <img v-if="user?.photo_url" :src="user.photo_url" style="object-fit: cover; width: 100%; height: 100%;" />
- <q-icon v-else name="mdi-account" size="44px" color="white" />
- </q-avatar>
- <q-btn
- round
- unelevated
- size="xs"
- icon="mdi-pencil-outline"
- class="profile-avatar-edit"
- color="white"
- text-color="violet-normal"
- :loading="uploadingAvatar"
- @click="triggerAvatarUpload"
- />
- <input
- ref="avatarInputRef"
- type="file"
- accept="image/*"
- style="display: none"
- @change="onAvatarSelected"
- />
- </div>
- <div class="column q-gutter-xs">
- <span class="text-white text-h6 text-weight-bold">{{ user?.name || '—' }}</span>
- <div class="row items-center q-gutter-xs">
- <q-badge
- :color="statusBadgeColor"
- class="text-capitalize"
- style="font-size:11px; padding: 3px 8px; border-radius: 20px;"
- >
- {{ statusLabel }}
- </q-badge>
- <q-btn
- v-if="user?.photo_url"
- flat
- round
- dense
- size="xs"
- icon="mdi-image-remove"
- color="white"
- :loading="removingAvatar"
- @click="onRemoveAvatar"
- />
- </div>
- <span class="text-white profile-validity text-caption">
- {{ $t('associado.validity') }} {{ user?.expiry_date || '—' }}
- </span>
- </div>
- </div>
- <div class="q-pa-md column">
- <div
- v-for="field in infoFields"
- :key="field.label"
- class="info-row row items-center no-wrap q-my-sm q-px-md"
- >
- <q-icon :name="field.icon" size="22px" class="info-icon q-mr-md" />
- <div class="column">
- <span class="info-label">{{ field.label }}</span>
- <span class="info-value">{{ field.value || '—' }}</span>
- </div>
- </div>
- </div>
- <div class="q-px-md">
- <div class="text-h6">{{ $t('associado.dependents') }}</div>
- <q-list v-if="dependents.length > 0" class="column q-mb-md">
- <div
- v-for="dep in dependents"
- :key="dep.id"
- class="dependent-row row items-center no-wrap q-my-md"
- >
- <q-avatar size="40px" class="dependent-avatar q-mr-md" text-color="white">
- {{ initials(dep.name) }}
- </q-avatar>
- <div class="col column">
- <span class="dependent-name">{{ dep.name }}</span>
- <span class="dependent-kinship">
- {{ $t(`associado.kinship_options.${dep.kinship}`) }}
- </span>
- </div>
- <q-badge
- :color="dependentStatusColor(dep.status)"
- class="q-mr-sm text-capitalize"
- style="font-size:11px; padding: 3px 8px; border-radius: 20px;"
- >
- {{ dependentStatusLabel(dep.status) }}
- </q-badge>
- </div>
- </q-list>
- <div v-else class="text-center q-py-md text-grey">
- {{ $t('associado.no_dependents') }}
- </div>
- <div class="flex justify-end q-pb-md">
- <q-btn
- unelevated
- icon="mdi-plus"
- :label="$t('common.actions.add').toUpperCase()"
- color="violet-normal"
- text-color="white"
- style="border-radius: 8px; padding: 8px 20px;"
- @click="onAddDependent"
- />
- </div>
- </div>
- </q-card>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, computed, onMounted, defineAsyncComponent, useTemplateRef } from "vue";
- import { useQuasar } from "quasar";
- import { useI18n } from "vue-i18n";
- import { userStore } from "src/stores/user";
- import { getDependentsByUser } from "src/api/profile";
- import { uploadMyAvatar, deleteMyAvatar } from "src/api/user";
- import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
- const AddEditDependentDialog = defineAsyncComponent(
- () => import("src/pages/associado/profile/components/AddEditDependentDialog.vue"),
- );
- const $q = useQuasar();
- const { t } = useI18n();
- const store = userStore();
- const user = ref(null);
- const dependents = ref([]);
- const uploadingAvatar = ref(false);
- const removingAvatar = ref(false);
- const avatarInputRef = useTemplateRef("avatarInputRef");
- const statusBadgeColor = computed(() => {
- switch (user.value?.status) {
- case "active": return "positive";
- case "inactive": return "grey";
- case "canceled": return "negative";
- default: return "grey";
- }
- });
- const statusLabel = computed(() => {
- const map = { active: t("common.status.active"), inactive: t("common.status.inactive"), canceled: t("common.status.canceled") };
- return map[user.value?.status] ?? user.value?.status ?? "—";
- });
- const infoFields = computed(() => [
- { icon: "mdi-card-account-details-outline", label: t("common.terms.cpf"), value: user.value?.cpf },
- { icon: "mdi-email-outline", label: "E-mail", value: user.value?.email },
- { icon: "mdi-briefcase-outline", label: t("associado.position"), value: user.value?.position?.name },
- { icon: "mdi-domain", label: t("associado.sector"), value: user.value?.sector?.name },
- { icon: "mdi-card-text-outline", label: t("associado.registration"), value: user.value?.registration },
- { icon: "mdi-calendar-outline", label: t("associado.admission_date"), value: user.value?.admission_date },
- ]);
- const initials = (name) => {
- if (!name) return "?";
- return name.split(" ").slice(0, 2).map((w) => w[0]).join("").toUpperCase();
- };
- const dependentStatusColor = (status) => {
- switch (status) {
- case "approved": return "positive";
- case "refused": return "negative";
- case "pending": return "warning-light";
- default: return "grey";
- }
- };
- const dependentStatusLabel = (status) => {
- const map = {
- approved: t("associado.dependent_statuses.approved"),
- refused: t("associado.dependent_statuses.refused"),
- pending: t("associado.dependent_statuses.pending"),
- };
- return map[status] ?? status ?? "—";
- };
- const loadUser = async () => {
- await store.fetchUser();
- user.value = store.user;
- };
- const loadDependents = async () => {
- if (!user.value?.id) return;
- try {
- dependents.value = await getDependentsByUser(user.value.id);
- } catch {
- dependents.value = [];
- }
- };
- onMounted(async () => {
- await loadUser();
- await loadDependents();
- });
- const triggerAvatarUpload = () => {
- avatarInputRef.value?.click();
- };
- const onAvatarSelected = async (event) => {
- const file = event.target.files?.[0];
- if (!file) return;
- uploadingAvatar.value = true;
- try {
- const updated = await uploadMyAvatar(file);
- store.user = updated;
- user.value = updated;
- $q.notify({ type: "positive", message: t("http.success") });
- } catch {
- $q.notify({ type: "negative", message: t("http.errors.failed") });
- } finally {
- uploadingAvatar.value = false;
- event.target.value = "";
- }
- };
- const onRemoveAvatar = () => {
- $q.dialog({
- title: t("common.ui.messages.confirm_action"),
- message: t("associado.remove_photo"),
- cancel: true,
- persistent: true,
- }).onOk(async () => {
- removingAvatar.value = true;
- try {
- const updated = await deleteMyAvatar();
- store.user = updated;
- user.value = updated;
- $q.notify({ type: "positive", message: t("http.success") });
- } catch {
- $q.notify({ type: "negative", message: t("http.errors.failed") });
- } finally {
- removingAvatar.value = false;
- }
- });
- };
- const onAddDependent = () => {
- $q.dialog({
- component: AddEditDependentDialog,
- componentProps: {
- userId: user.value.id,
- title: () => t("common.actions.add") + " " + t("associado.dependent"),
- },
- }).onOk(() => loadDependents());
- };
- </script>
- <style lang="scss" scoped>
- @use "src/css/quasar.variables.scss" as *;
- .profile-card {
- border-radius: 12px;
- overflow: hidden;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
- }
- .profile-header {
- background: linear-gradient(135deg, $violet-normal 0%, $violet-dark 100%);
- }
- .profile-avatar-wrap {
- position: relative;
- flex-shrink: 0;
- }
- .profile-avatar {
- background: rgba(255, 255, 255, 0.18) !important;
- // border: 2px solid rgba(255, 255, 255, 0.45);
- }
- .profile-avatar-edit {
- position: absolute;
- bottom: -2px;
- right: -2px;
- width: 22px;
- height: 22px;
- min-height: 22px;
- }
- .profile-validity {
- opacity: 0.85;
- }
- .info-row {
- background: $violet-light;
- border-radius: 8px;
- padding: 12px 16px;
- }
- .info-icon {
- color: $violet-normal;
- flex-shrink: 0;
- }
- .info-label {
- font-size: 11px;
- color: $color-text-2;
- line-height: 1.2;
- }
- .info-value {
- font-size: 14px;
- color: $color-text;
- font-weight: 500;
- line-height: 1.4;
- }
- .dependent-row {
- background: $violet-light;
- border-radius: 8px;
- padding: 10px 12px;
- }
- .dependent-avatar {
- background: $violet-normal !important;
- font-size: 14px;
- font-weight: 700;
- flex-shrink: 0;
- }
- .dependent-name {
- font-size: 14px;
- font-weight: 600;
- color: $color-text;
- line-height: 1.3;
- }
- .dependent-kinship {
- font-size: 12px;
- color: $color-text-2;
- line-height: 1.2;
- }
- </style>
|