feat(avatar) : replace initials with UserAvatar component everywhere
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -79,7 +79,16 @@
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 py-3 text-neutral-600">{{ getProjectName(ticket.project) }}</td>
|
||||
<td class="px-3 py-3 text-neutral-600">{{ getSubmitterName(ticket.submittedBy) }}</td>
|
||||
<td class="px-3 py-3 text-neutral-600">
|
||||
<div class="flex items-center gap-2">
|
||||
<UserAvatar
|
||||
v-if="getSubmitterUser(ticket.submittedBy)"
|
||||
:user="getSubmitterUser(ticket.submittedBy)!"
|
||||
size="sm"
|
||||
/>
|
||||
{{ getSubmitterName(ticket.submittedBy) }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-3 py-3 text-neutral-400">{{ formatDate(ticket.createdAt) }}</td>
|
||||
<td class="px-3 py-3">
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -216,7 +225,7 @@ const { t } = useI18n()
|
||||
const clientTicketService = useClientTicketService()
|
||||
const projectService = useProjectService()
|
||||
const userService = useUserService()
|
||||
const { typeBadgeClass, statusBadgeClass, formatDate } = useClientTicketHelpers()
|
||||
const { typeBadgeClass, statusBadgeClass, formatDate, getAvailableStatusTransitions } = useClientTicketHelpers()
|
||||
|
||||
const tickets = ref<ClientTicket[]>([])
|
||||
const projects = ref<Project[]>([])
|
||||
@@ -261,19 +270,7 @@ const detailTicket = ref<ClientTicket | null>(null)
|
||||
|
||||
const availableStatusTransitions = computed(() => {
|
||||
if (!statusTarget.value) return []
|
||||
const current = statusTarget.value.status
|
||||
const allStatuses: { label: string; value: ClientTicketStatus }[] = [
|
||||
{ label: t('clientTicket.status.new'), value: 'new' },
|
||||
{ label: t('clientTicket.status.in_progress'), value: 'in_progress' },
|
||||
{ label: t('clientTicket.status.done'), value: 'done' },
|
||||
{ label: t('clientTicket.status.rejected'), value: 'rejected' },
|
||||
]
|
||||
// Filter out forbidden transitions
|
||||
return allStatuses.filter(s => {
|
||||
if (s.value === current) return false
|
||||
if ((current === 'done' || current === 'rejected') && s.value === 'new') return false
|
||||
return true
|
||||
})
|
||||
return getAvailableStatusTransitions(statusTarget.value.status, t)
|
||||
})
|
||||
|
||||
function getProjectName(iri: string): string {
|
||||
@@ -291,6 +288,14 @@ function getSubmitterName(iri: string | null): string {
|
||||
return users.value.find(u => u.id === id)?.username ?? ''
|
||||
}
|
||||
|
||||
function getSubmitterUser(iri: string | null): UserData | undefined {
|
||||
if (!iri) return undefined
|
||||
const match = iri.match(/\/api\/users\/(\d+)/)
|
||||
if (!match) return undefined
|
||||
const id = Number(match[1])
|
||||
return users.value.find(u => u.id === id)
|
||||
}
|
||||
|
||||
function openDetail(ticket: ClientTicket) {
|
||||
detailTicket.value = ticket
|
||||
detailOpen.value = true
|
||||
|
||||
@@ -44,13 +44,12 @@
|
||||
>
|
||||
{{ tag.label }}
|
||||
</span>
|
||||
<span
|
||||
<UserAvatar
|
||||
v-if="task.assignee"
|
||||
class="ml-auto flex h-5 w-5 items-center justify-center rounded-full bg-primary-500 text-[10px] font-bold text-white"
|
||||
:title="task.assignee.username"
|
||||
>
|
||||
{{ task.assignee.username.substring(0, 2).toUpperCase() }}
|
||||
</span>
|
||||
:user="task.assignee"
|
||||
size="xs"
|
||||
class="ml-auto"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="ml-auto flex h-5 w-5 items-center justify-center rounded-full bg-neutral-200 text-neutral-400"
|
||||
|
||||
@@ -10,12 +10,14 @@
|
||||
<div class="ml-auto flex items-center gap-4 text-xl text-white sm:gap-8">
|
||||
<NotificationBell />
|
||||
<div class="group relative flex gap-2 sm:gap-4">
|
||||
<Icon name="mdi:account-circle-outline" class="self-center cursor-pointer" size="36" />
|
||||
<UserAvatar v-if="user" :user="user" size="md" class="cursor-pointer" />
|
||||
<Icon v-else name="mdi:account-circle-outline" class="self-center cursor-pointer" size="36" />
|
||||
<p class="hidden self-center cursor-pointer sm:block">{{ user?.username }}</p>
|
||||
<div class="invisible absolute right-0 top-full z-50 mt-2 w-44 rounded-md border border-neutral-200 bg-white py-1 text-sm text-neutral-800 opacity-0 shadow-lg transition-all group-hover:visible group-hover:opacity-100">
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full px-3 py-2 text-left hover:bg-neutral-100"
|
||||
@click="navigateTo('/profile')"
|
||||
>
|
||||
Mon profil
|
||||
</button>
|
||||
@@ -43,7 +45,7 @@ defineProps<{
|
||||
const auth = useAuthStore()
|
||||
const ui = useUiStore()
|
||||
|
||||
const handleLogout = async () => {
|
||||
async function handleLogout() {
|
||||
await auth.logout()
|
||||
await navigateTo('/login')
|
||||
}
|
||||
|
||||
@@ -46,13 +46,11 @@
|
||||
>
|
||||
{{ task.group.title }}
|
||||
</span>
|
||||
<span
|
||||
<UserAvatar
|
||||
v-if="task.assignee"
|
||||
class="flex h-5 w-5 items-center justify-center rounded-full bg-primary-500 text-[10px] font-bold text-white"
|
||||
:title="task.assignee.username"
|
||||
>
|
||||
{{ task.assignee.username.substring(0, 2).toUpperCase() }}
|
||||
</span>
|
||||
:user="task.assignee"
|
||||
size="xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -130,7 +128,7 @@ const filteredTasks = computed(() => {
|
||||
async function loadData() {
|
||||
const [p, t, s, e, pr, ty, g, u] = await Promise.all([
|
||||
projectService.getById(projectId.value),
|
||||
taskService.getByProjectArchived(projectId.value),
|
||||
taskService.getByProject(projectId.value, true),
|
||||
statusService.getAll(),
|
||||
effortService.getAll(),
|
||||
priorityService.getAll(),
|
||||
|
||||
Reference in New Issue
Block a user