Profile picture display has been standardized across the board.

This commit is contained in:
2026-10-06 14:23:59 +03:30
parent bc01384551
commit 43717392b2
9 changed files with 154 additions and 21 deletions
+27 -4
View File
@@ -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"
/>
+2 -2
View File
@@ -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"