Profile picture display has been standardized across the board.
This commit is contained in:
@@ -1,29 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { personInitials } from '@/lib/person-insights';
|
||||
import { DEFAULT_USER_AVATAR, primeUserAvatar, userAvatarSrc } from '@/lib/userAvatars';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
name?: string | null;
|
||||
src?: string | null;
|
||||
/** شناسه کاربر سیستم؛ اگر داده شود عکس پروفایل واقعی او نمایش داده میشود */
|
||||
userId?: string | null;
|
||||
hasAvatar?: boolean;
|
||||
size?: number;
|
||||
}>(),
|
||||
{
|
||||
name: '',
|
||||
src: '/brand/user-avatar.png',
|
||||
src: undefined,
|
||||
userId: null,
|
||||
hasAvatar: undefined,
|
||||
size: 28,
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [props.userId, props.hasAvatar] as const,
|
||||
([id, has]) => {
|
||||
if (id) primeUserAvatar(id, has);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const resolvedSrc = computed(() => {
|
||||
if (props.src !== undefined) return props.src;
|
||||
if (props.userId) return userAvatarSrc(props.userId);
|
||||
return DEFAULT_USER_AVATAR;
|
||||
});
|
||||
|
||||
const initials = computed(() => personInitials(props.name));
|
||||
const failed = ref(false);
|
||||
watch(resolvedSrc, () => {
|
||||
failed.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="user-avatar" :style="{ width: `${size}px`, height: `${size}px` }" :title="name || undefined">
|
||||
<img
|
||||
v-if="src && !failed"
|
||||
:src="src"
|
||||
v-if="resolvedSrc && !failed"
|
||||
:src="resolvedSrc"
|
||||
:alt="name || 'آواتار'"
|
||||
@error="failed = true"
|
||||
/>
|
||||
|
||||
@@ -89,7 +89,7 @@ onBeforeUnmount(() => document.removeEventListener('mousedown', onDocClick));
|
||||
<div ref="root" class="ums" :class="{ disabled }" @keydown="onKey">
|
||||
<div class="chips">
|
||||
<span v-for="u in selected" :key="u.id" class="chip">
|
||||
<UserAvatar :name="u.fullName" :size="22" />
|
||||
<UserAvatar :name="u.fullName" :user-id="u.id" :size="22" />
|
||||
<span class="chip-name">{{ u.fullName }}</span>
|
||||
<button
|
||||
v-if="canRemove && !disabled"
|
||||
@@ -130,7 +130,7 @@ onBeforeUnmount(() => document.removeEventListener('mousedown', onDocClick));
|
||||
<span class="check" aria-hidden="true">
|
||||
<UiIcon v-if="modelValue.includes(u.id)" name="check-plain" :size="12" />
|
||||
</span>
|
||||
<UserAvatar :name="u.fullName" :size="26" />
|
||||
<UserAvatar :name="u.fullName" :user-id="u.id" :size="26" />
|
||||
<span class="row-name">{{ u.fullName }}</span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import { userAvatarSrc } from '@/lib/userAvatars';
|
||||
import type { PipelineAccent, PipelineDeal, PipelineUiCopy } from '@/lib/pipeline-board';
|
||||
import {
|
||||
DEAL_BOARD_COPY,
|
||||
@@ -35,6 +36,9 @@ const colors = computed(() => PIPELINE_ACCENT[props.accent]);
|
||||
const assigneeTitle = computed(() =>
|
||||
(props.deal.assigneeNames ?? []).filter(Boolean).join('، ') || 'مسئول',
|
||||
);
|
||||
const avatarSrc = computed(() =>
|
||||
userAvatarSrc(props.deal.ownerId || props.deal.assigneeIds?.[0] || null),
|
||||
);
|
||||
const extraAssignees = computed(() =>
|
||||
Math.max(0, (props.deal.assigneeNames?.length ?? 0) - 1),
|
||||
);
|
||||
@@ -70,7 +74,7 @@ const extraAssignees = computed(() =>
|
||||
</button>
|
||||
<img
|
||||
class="avatar"
|
||||
src="/brand/user-avatar.png"
|
||||
:src="avatarSrc"
|
||||
alt=""
|
||||
width="32"
|
||||
height="32"
|
||||
|
||||
Reference in New Issue
Block a user