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>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-3 text-neutral-600">{{ getProjectName(ticket.project) }}</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 text-neutral-400">{{ formatDate(ticket.createdAt) }}</td>
|
||||||
<td class="px-3 py-3">
|
<td class="px-3 py-3">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
@@ -216,7 +225,7 @@ const { t } = useI18n()
|
|||||||
const clientTicketService = useClientTicketService()
|
const clientTicketService = useClientTicketService()
|
||||||
const projectService = useProjectService()
|
const projectService = useProjectService()
|
||||||
const userService = useUserService()
|
const userService = useUserService()
|
||||||
const { typeBadgeClass, statusBadgeClass, formatDate } = useClientTicketHelpers()
|
const { typeBadgeClass, statusBadgeClass, formatDate, getAvailableStatusTransitions } = useClientTicketHelpers()
|
||||||
|
|
||||||
const tickets = ref<ClientTicket[]>([])
|
const tickets = ref<ClientTicket[]>([])
|
||||||
const projects = ref<Project[]>([])
|
const projects = ref<Project[]>([])
|
||||||
@@ -261,19 +270,7 @@ const detailTicket = ref<ClientTicket | null>(null)
|
|||||||
|
|
||||||
const availableStatusTransitions = computed(() => {
|
const availableStatusTransitions = computed(() => {
|
||||||
if (!statusTarget.value) return []
|
if (!statusTarget.value) return []
|
||||||
const current = statusTarget.value.status
|
return getAvailableStatusTransitions(statusTarget.value.status, t)
|
||||||
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
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function getProjectName(iri: string): string {
|
function getProjectName(iri: string): string {
|
||||||
@@ -291,6 +288,14 @@ function getSubmitterName(iri: string | null): string {
|
|||||||
return users.value.find(u => u.id === id)?.username ?? ''
|
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) {
|
function openDetail(ticket: ClientTicket) {
|
||||||
detailTicket.value = ticket
|
detailTicket.value = ticket
|
||||||
detailOpen.value = true
|
detailOpen.value = true
|
||||||
|
|||||||
@@ -44,13 +44,12 @@
|
|||||||
>
|
>
|
||||||
{{ tag.label }}
|
{{ tag.label }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<UserAvatar
|
||||||
v-if="task.assignee"
|
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"
|
:user="task.assignee"
|
||||||
:title="task.assignee.username"
|
size="xs"
|
||||||
>
|
class="ml-auto"
|
||||||
{{ task.assignee.username.substring(0, 2).toUpperCase() }}
|
/>
|
||||||
</span>
|
|
||||||
<span
|
<span
|
||||||
v-else
|
v-else
|
||||||
class="ml-auto flex h-5 w-5 items-center justify-center rounded-full bg-neutral-200 text-neutral-400"
|
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">
|
<div class="ml-auto flex items-center gap-4 text-xl text-white sm:gap-8">
|
||||||
<NotificationBell />
|
<NotificationBell />
|
||||||
<div class="group relative flex gap-2 sm:gap-4">
|
<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>
|
<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">
|
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="block w-full px-3 py-2 text-left hover:bg-neutral-100"
|
class="block w-full px-3 py-2 text-left hover:bg-neutral-100"
|
||||||
|
@click="navigateTo('/profile')"
|
||||||
>
|
>
|
||||||
Mon profil
|
Mon profil
|
||||||
</button>
|
</button>
|
||||||
@@ -43,7 +45,7 @@ defineProps<{
|
|||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
const ui = useUiStore()
|
const ui = useUiStore()
|
||||||
|
|
||||||
const handleLogout = async () => {
|
async function handleLogout() {
|
||||||
await auth.logout()
|
await auth.logout()
|
||||||
await navigateTo('/login')
|
await navigateTo('/login')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,13 +46,11 @@
|
|||||||
>
|
>
|
||||||
{{ task.group.title }}
|
{{ task.group.title }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<UserAvatar
|
||||||
v-if="task.assignee"
|
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"
|
:user="task.assignee"
|
||||||
:title="task.assignee.username"
|
size="xs"
|
||||||
>
|
/>
|
||||||
{{ task.assignee.username.substring(0, 2).toUpperCase() }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -130,7 +128,7 @@ const filteredTasks = computed(() => {
|
|||||||
async function loadData() {
|
async function loadData() {
|
||||||
const [p, t, s, e, pr, ty, g, u] = await Promise.all([
|
const [p, t, s, e, pr, ty, g, u] = await Promise.all([
|
||||||
projectService.getById(projectId.value),
|
projectService.getById(projectId.value),
|
||||||
taskService.getByProjectArchived(projectId.value),
|
taskService.getByProject(projectId.value, true),
|
||||||
statusService.getAll(),
|
statusService.getAll(),
|
||||||
effortService.getAll(),
|
effortService.getAll(),
|
||||||
priorityService.getAll(),
|
priorityService.getAll(),
|
||||||
|
|||||||
Reference in New Issue
Block a user