Compare commits
7
Commits
4c6f21ef78
...
3157946186
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3157946186 | ||
|
|
67d30a6f32 | ||
|
|
3082794436 | ||
|
|
6f651eeaeb | ||
|
|
baf0fa186e | ||
|
|
9c65fc05f4 | ||
|
|
1b42c0d916 |
No files matched your search
+7
-3
@@ -1,13 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { useEventSource } from '@vueuse/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onMounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useStatusStore } from '@/stores/status.ts'
|
||||
import { useIssueStore } from '@/stores/issue.ts'
|
||||
import { useProjectStore } from '@/stores/project.ts'
|
||||
|
||||
const route = useRoute()
|
||||
const issueStore = useIssueStore()
|
||||
const statusStore = useStatusStore()
|
||||
const projectStore = useProjectStore()
|
||||
const { currentProjectId } = storeToRefs(projectStore)
|
||||
|
||||
interface IssueSseEvent {
|
||||
type: string
|
||||
@@ -25,7 +29,7 @@ const { data } = useEventSource<string[], IssueSseEvent>(
|
||||
console.log('Failed to connect EventSource after 3 retries')
|
||||
},
|
||||
},
|
||||
autoConnect: true,
|
||||
autoConnect: false,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -35,7 +39,7 @@ watch(data, () => {
|
||||
data.value?.type === 'issue.updated' ||
|
||||
data.value?.type === 'issue.deleted'
|
||||
) {
|
||||
issueStore.getStat()
|
||||
issueStore.getStat(currentProjectId.value)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -46,7 +50,7 @@ const layout = computed(() => {
|
||||
|
||||
onMounted(async () => {
|
||||
await statusStore.list()
|
||||
await issueStore.getStat()
|
||||
await issueStore.getStat(currentProjectId.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface Issue {
|
||||
reporterId: string
|
||||
assignee?: User
|
||||
reporter?: User
|
||||
dueDate: string | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
@@ -83,6 +84,7 @@ export async function itemDelete(id: number) {
|
||||
return true
|
||||
}
|
||||
|
||||
export async function itemStat() {
|
||||
return apiClient.get<IssueStat>('/api/issues/stats')
|
||||
export async function itemStat(projectId?: string) {
|
||||
const query = toQuery({ projectId: projectId })
|
||||
return apiClient.get<IssueStat>(`/api/issues/stats${query}`)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
label: string
|
||||
value: number | string
|
||||
alert?: boolean
|
||||
}
|
||||
|
||||
const { label, value, alert } = defineProps<Props>()
|
||||
const emit = defineEmits<{ click: [] }>()
|
||||
|
||||
function onClick() {
|
||||
emit('click')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card :border="!alert" variant="flat" :color="alert ? 'error' : undefined" @click="onClick">
|
||||
<v-card-text>
|
||||
<div class="text-label-large" :class="{ 'text-medium-emphasis': !alert }">{{ label }}</div>
|
||||
<div class="text-headline-large">{{ value }}</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -10,18 +10,19 @@ const emit = defineEmits<{ created: [issue: Issue] }>()
|
||||
|
||||
const showForm = ref(false)
|
||||
const newIssue = reactive({
|
||||
key: 'type',
|
||||
key: '',
|
||||
title: '',
|
||||
})
|
||||
|
||||
const onCreate = async () => {
|
||||
const projectId = projectStore.current?.id
|
||||
if (!projectId) return
|
||||
const project = projectStore.current
|
||||
if (!project) return
|
||||
|
||||
const created = await issueStore.create({ ...newIssue, projectId })
|
||||
const created = await issueStore.create({ ...newIssue, projectId: project.id, key: project.key })
|
||||
if (!created) return
|
||||
|
||||
emit('created', created)
|
||||
newIssue.key = ''
|
||||
newIssue.title = ''
|
||||
showForm.value = false
|
||||
}
|
||||
|
||||
@@ -2,16 +2,19 @@
|
||||
import { computed } from 'vue'
|
||||
import AssigneeFilter from '@/components/AssigneeFilter.vue'
|
||||
import TextFilter from '@/components/TextFilter.vue'
|
||||
import StatusFilter from '@/components/StatusFilter.vue'
|
||||
|
||||
interface Props {
|
||||
text?: string | null
|
||||
assigneeId?: string | null
|
||||
status?: string | null
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), { text: null, assigneeId: null })
|
||||
const emit = defineEmits<{
|
||||
'update:text': [value: string | null]
|
||||
'update:assigneeId': [value: string | null]
|
||||
'update:status': [value: string | null]
|
||||
'update:clear': []
|
||||
}>()
|
||||
|
||||
@@ -20,11 +23,16 @@ const selectedAssignees = computed(() => (props.assigneeId ? [props.assigneeId]
|
||||
function onAssigneeChanged(value: string[]) {
|
||||
emit('update:assigneeId', value.at(-1) ?? null)
|
||||
}
|
||||
|
||||
function onStatusChanged(alias: string) {
|
||||
emit('update:status', alias)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="filter-grid ga-4 align-center">
|
||||
<text-filter :model-value="text" @update:model-value="emit('update:text', $event)" />
|
||||
<status-filter :status="status" @changed="onStatusChanged" />
|
||||
<assignee-filter :selected="selectedAssignees" @changed="onAssigneeChanged" />
|
||||
<v-btn prepend-icon="mdi-close" variant="tonal" @click="emit('update:clear')">Clear Filters</v-btn>
|
||||
</div>
|
||||
@@ -33,6 +41,6 @@ function onAssigneeChanged(value: string[]) {
|
||||
<style scoped lang="scss">
|
||||
.filter-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto auto;
|
||||
grid-template-columns: 1fr auto auto auto;
|
||||
}
|
||||
</style>
|
||||
@@ -46,10 +46,9 @@ const onAssigneeChanged = async (assignee: string) => {
|
||||
v-if="[3, 4].includes(issue.priority) && ['OPEN', 'IN_PROGRESS'].includes(issue.status)"
|
||||
icon="mdi-alert-box"
|
||||
color="error"
|
||||
></v-icon>
|
||||
/>
|
||||
<div :class="{ 'text-decoration-line-through': issue.status === 'DONE' }">{{ issue.title }}</div>
|
||||
</div>
|
||||
<!-- <div :class="{ 'text-decoration-line-through': issue.status === 'DONE' }">{{ issue.content }}</div>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -61,7 +60,6 @@ const onAssigneeChanged = async (assignee: string) => {
|
||||
:model-value="selected"
|
||||
@update:model-value="emit('toggleSelect', issue.id)"
|
||||
/>
|
||||
<v-icon icon="mdi-square" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -31,10 +31,17 @@ onMounted(async () => {
|
||||
|
||||
<template>
|
||||
<div v-if="projects.length" class="d-flex align-center ga-2">
|
||||
Project:
|
||||
<div>Project:</div>
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn border density="comfortable" variant="flat" :text="currentProjectName" v-bind="props" />
|
||||
<v-btn density="comfortable" v-bind="props">
|
||||
<div class="d-flex ga-1 align-center">
|
||||
<v-chip v-if="current" size="small" variant="flat" color="primary" density="compact">
|
||||
{{ current.key }}
|
||||
</v-chip>
|
||||
<div>{{ currentProjectName }}</div>
|
||||
</div>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<v-list
|
||||
@@ -48,9 +55,9 @@ onMounted(async () => {
|
||||
@update:selected="select"
|
||||
>
|
||||
<v-list-item slim v-for="project in projects" :key="project.id" :value="project.id">
|
||||
<v-list-item-title>
|
||||
<v-list-item-title class="d-flex ga-1 align-center">
|
||||
<v-chip size="small" color="primary" variant="flat" density="compact">{{ project.key }}</v-chip>
|
||||
{{ project.name }}
|
||||
<div>{{ project.name }}</div>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import { useStatusStore } from '@/stores/status.ts'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
interface Props {
|
||||
status?: string | null
|
||||
}
|
||||
|
||||
const { status } = defineProps<Props>()
|
||||
const emit = defineEmits<{ changed: [value: string] }>()
|
||||
const statusStore = useStatusStore()
|
||||
const selected = ref(status)
|
||||
const onUpdate = (val: unknown) => emit('changed', val as string)
|
||||
watch(
|
||||
() => status,
|
||||
(val) => {
|
||||
selected.value = val
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-btn-toggle
|
||||
v-model="selected"
|
||||
divided
|
||||
size="default"
|
||||
density="compact"
|
||||
variant="text"
|
||||
@update:model-value="onUpdate"
|
||||
>
|
||||
<v-btn slim v-for="el in statusStore.statuses" :color="el.color" :value="el.alias" :key="el.alias" rounded="0">
|
||||
<div class="text-default">{{ el.name }}</div>
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -25,14 +25,9 @@ const menu = ref([
|
||||
value: 2,
|
||||
props: { prependIcon: 'mdi-list-status', to: '/issues', requiresAuth: true },
|
||||
},
|
||||
{
|
||||
title: 'summary',
|
||||
value: 3,
|
||||
props: { prependIcon: 'mdi-view-dashboard', to: '/summary', requiresAuth: true },
|
||||
},
|
||||
{
|
||||
title: 'games',
|
||||
value: 4,
|
||||
value: 3,
|
||||
props: { prependIcon: 'mdi-view-dashboard', to: '/games', requiresAuth: false },
|
||||
},
|
||||
])
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import ProfileView from '@/views/users/ProfileView.vue'
|
||||
import { issuesRoutes } from '@/router/issues.ts'
|
||||
import { reportsRoutes } from '@/router/reports.ts'
|
||||
import { summaryRoutes } from '@/router/summary.ts'
|
||||
import HomeView from '@/views/HomeView.vue'
|
||||
import VerifyEmailView from '@/views/users/VerifyEmailView.vue'
|
||||
import SignUpView from '@/views/users/SignUpView.vue'
|
||||
@@ -44,8 +42,6 @@ const router = createRouter({
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
issuesRoutes,
|
||||
summaryRoutes,
|
||||
reportsRoutes,
|
||||
gamesRoutes,
|
||||
settingsRoutes,
|
||||
],
|
||||
|
||||
+2
-2
@@ -75,9 +75,9 @@ export const useIssueStore = defineStore('item', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function getStat(): Promise<IssueStat | undefined> {
|
||||
async function getStat(projectId?: string | null): Promise<IssueStat | undefined> {
|
||||
try {
|
||||
stats.value = await itemStat()
|
||||
stats.value = await itemStat(projectId ?? undefined)
|
||||
|
||||
return stats.value
|
||||
} catch (e) {
|
||||
|
||||
+207
-3
@@ -1,7 +1,211 @@
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useIssueStore } from '@/stores/issue.ts'
|
||||
import { useProjectStore } from '@/stores/project.ts'
|
||||
import { useStatusStore } from '@/stores/status.ts'
|
||||
import { authClient } from '@/lib/auth-client.ts'
|
||||
import type { Issue } from '@/api/issue.api.ts'
|
||||
import type { Project } from '@/api/project.api.ts'
|
||||
import DashboardCountCard from '@/components/DashboardCountCard.vue'
|
||||
|
||||
const ACTIVE_STATUSES: Issue['status'][] = ['OPEN', 'IN_PROGRESS']
|
||||
|
||||
const PRIORITY_ICONS: Record<number, string> = {
|
||||
4: 'mdi-chevron-double-up',
|
||||
3: 'mdi-chevron-up',
|
||||
2: 'mdi-equal',
|
||||
1: 'mdi-chevron-down',
|
||||
0: 'mdi-chevron-double-down',
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const session = authClient.useSession()
|
||||
|
||||
const issueStore = useIssueStore()
|
||||
const projectStore = useProjectStore()
|
||||
const statusStore = useStatusStore()
|
||||
|
||||
const { stats } = storeToRefs(issueStore)
|
||||
const { projects, currentProjectId } = storeToRefs(projectStore)
|
||||
|
||||
const currentUserId = computed(() => session.value.data?.user.id ?? null)
|
||||
|
||||
const myIssues = ref<Issue[]>([])
|
||||
const loadingMyIssues = ref(false)
|
||||
|
||||
async function loadMyIssues(userId: string) {
|
||||
loadingMyIssues.value = true
|
||||
try {
|
||||
const res = await issueStore.list({ page: 1, limit: 50, assigneeId: userId })
|
||||
myIssues.value = res.list.filter((issue) => ACTIVE_STATUSES.includes(issue.status))
|
||||
} finally {
|
||||
loadingMyIssues.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
currentUserId,
|
||||
(id) => {
|
||||
if (id) void loadMyIssues(id)
|
||||
else myIssues.value = []
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
currentProjectId,
|
||||
(id) => {
|
||||
void issueStore.getStat(id)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function isOverdue(issue: Issue) {
|
||||
return !!issue.dueDate && new Date(issue.dueDate).getTime() < Date.now()
|
||||
}
|
||||
|
||||
const overdueCount = computed(() => myIssues.value.filter(isOverdue).length)
|
||||
|
||||
const sortedMyIssues = computed(() =>
|
||||
[...myIssues.value].sort((a, b) => {
|
||||
if (!a.dueDate && !b.dueDate) return 0
|
||||
if (!a.dueDate) return 1
|
||||
if (!b.dueDate) return -1
|
||||
return new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime()
|
||||
}),
|
||||
)
|
||||
|
||||
function statCount(status: Issue['status']) {
|
||||
return stats.value?.byStatus.find((el) => el.status === status)?.count ?? 0
|
||||
}
|
||||
|
||||
function statusMeta(status: string) {
|
||||
const found = statusStore.statuses?.find((el) => el.alias === status)
|
||||
return { label: found?.name ?? status, color: found?.color ?? 'grey' }
|
||||
}
|
||||
|
||||
function priorityIcon(priority: number) {
|
||||
return PRIORITY_ICONS[priority] ?? PRIORITY_ICONS[2]
|
||||
}
|
||||
|
||||
function priorityColor(priority: number) {
|
||||
if (priority >= 3) return 'error'
|
||||
if (priority === 2) return 'success'
|
||||
return 'primary'
|
||||
}
|
||||
|
||||
function goToIssue(issue: Issue) {
|
||||
router.push(`/issues/${issue.key}`)
|
||||
}
|
||||
|
||||
function openProject(project: Project) {
|
||||
projectStore.select(project)
|
||||
router.push('/issues')
|
||||
}
|
||||
|
||||
function onClickCard(status?: string) {
|
||||
router.push({ path: '/issues', query: { status } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>Home</div>
|
||||
<div>
|
||||
<div v-if="session.data" class="d-flex flex-column ga-2">
|
||||
<div class="dashboard_grid">
|
||||
<dashboard-count-card label="Всего задач" :value="stats?.total ?? 0" @click="onClickCard()" />
|
||||
<dashboard-count-card label="Открыто" :value="statCount('OPEN')" @click="onClickCard('OPEN')" />
|
||||
<dashboard-count-card label="В работе" :value="statCount('IN_PROGRESS')" @click="onClickCard('IN_PROGRESS')" />
|
||||
<dashboard-count-card label="Просрочено" :value="overdueCount" :alert="overdueCount > 0" />
|
||||
</div>
|
||||
|
||||
<v-row density="compact">
|
||||
<v-col cols="12" md="8">
|
||||
<v-card border>
|
||||
<v-card-title class="d-flex align-center justify-space-between">
|
||||
<span>Мои задачи</span>
|
||||
<v-btn size="small" variant="text" to="/issues">Все задачи</v-btn>
|
||||
</v-card-title>
|
||||
<v-divider />
|
||||
<v-list v-if="sortedMyIssues.length" density="compact">
|
||||
<v-list-item
|
||||
v-for="issue in sortedMyIssues"
|
||||
:key="issue.id"
|
||||
class="cursor-pointer"
|
||||
@click="goToIssue(issue)"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon :icon="priorityIcon(issue.priority)" :color="priorityColor(issue.priority)" size="small" />
|
||||
</template>
|
||||
|
||||
<template #title>
|
||||
<div class="d-flex ga-2 align-center">
|
||||
<span class="text-medium-emphasis">{{ issue.key }}</span>
|
||||
<span>{{ issue.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #append>
|
||||
<div class="d-flex ga-2 align-center">
|
||||
<v-chip
|
||||
v-if="issue.dueDate"
|
||||
density="compact"
|
||||
variant="flat"
|
||||
:color="isOverdue(issue) ? 'error' : undefined"
|
||||
>
|
||||
{{ new Date(issue.dueDate).toLocaleDateString('ru-RU') }}
|
||||
</v-chip>
|
||||
<v-chip density="compact" variant="flat" :color="statusMeta(issue.status).color">
|
||||
{{ statusMeta(issue.status).label }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-card-text v-else class="text-medium-emphasis">
|
||||
{{ loadingMyIssues ? 'Загрузка…' : 'Нет активных задач' }}
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="4">
|
||||
<v-card border>
|
||||
<v-card-title class="d-flex align-center justify-space-between">
|
||||
<span>Проекты</span>
|
||||
<v-btn size="small" variant="text" to="/settings/projects">Все проекты</v-btn>
|
||||
</v-card-title>
|
||||
<v-divider />
|
||||
<v-list v-if="projects.length" density="compact">
|
||||
<v-list-item
|
||||
v-for="project in projects.slice(0, 6)"
|
||||
:key="project.id"
|
||||
class="cursor-pointer"
|
||||
@click="openProject(project)"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-chip size="small" color="primary" variant="flat" density="compact">{{ project.key }}</v-chip>
|
||||
</template>
|
||||
<template #title>
|
||||
<span class="ml-2">{{ project.name }}</span>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-card-text v-else class="text-medium-emphasis">Нет проектов</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
<style scoped lang="scss">
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.dashboard_grid {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
</style>
|
||||
@@ -27,6 +27,7 @@ const serverItems = ref<Issue[]>([])
|
||||
|
||||
const text = ref<string | null>(null)
|
||||
const assigneeId = ref<string | null>(null)
|
||||
const status = ref<string | null>(null)
|
||||
const selected = ref<[string] | []>([])
|
||||
const batch = ref<number[]>([])
|
||||
const deleting = ref(false)
|
||||
@@ -36,6 +37,7 @@ async function fetch() {
|
||||
page: page.value,
|
||||
limit: itemsPerPage.value,
|
||||
text: text.value,
|
||||
status: status.value,
|
||||
assigneeId: assigneeId.value,
|
||||
projectId: currentProjectId.value,
|
||||
})
|
||||
@@ -55,6 +57,7 @@ function syncQuery() {
|
||||
|
||||
if (text.value) query.text = text.value
|
||||
if (assigneeId.value) query.assignee = assigneeId.value
|
||||
if (status.value) query.status = status.value
|
||||
if (selected.value.length) query.selected_issue = selected.value[0]
|
||||
|
||||
router.replace({ query })
|
||||
@@ -66,6 +69,7 @@ watch(
|
||||
page.value = query.page ? Number(query.page) : 1
|
||||
text.value = query.text ? String(query.text) : null
|
||||
assigneeId.value = query.assignee ? String(query.assignee) : null
|
||||
status.value = query.status ? String(query.status) : null
|
||||
selected.value = query.selected_issue ? [String(query.selected_issue)] : []
|
||||
},
|
||||
{ immediate: true },
|
||||
@@ -79,9 +83,13 @@ watch(currentProjectId, async () => {
|
||||
clearBatch()
|
||||
close()
|
||||
await load()
|
||||
await issueStore.getStat(currentProjectId.value)
|
||||
})
|
||||
|
||||
onMounted(async () => await load())
|
||||
onMounted(async () => {
|
||||
await load()
|
||||
await issueStore.getStat(currentProjectId.value)
|
||||
})
|
||||
const gridTemplate = computed(() => (selected.value.length ? '1fr 1fr' : '1fr'))
|
||||
const goToIssue = (key: string[]) => router.push(`/issues/${key[0]}`)
|
||||
|
||||
@@ -102,9 +110,16 @@ async function onAssigneeChanged(value: string | null) {
|
||||
await load()
|
||||
}
|
||||
|
||||
async function onStatusChanged(value: string | null) {
|
||||
status.value = value
|
||||
page.value = 1
|
||||
await load()
|
||||
}
|
||||
|
||||
async function onClear() {
|
||||
text.value = null
|
||||
assigneeId.value = null
|
||||
status.value = null
|
||||
page.value = 1
|
||||
|
||||
await load()
|
||||
@@ -175,8 +190,10 @@ async function onDeleteBatch() {
|
||||
class="mb-2"
|
||||
:text="text"
|
||||
:assignee-id="assigneeId"
|
||||
:status="status"
|
||||
@update:text="onTextChanged"
|
||||
@update:assignee-id="onAssigneeChanged"
|
||||
@update:status="onStatusChanged"
|
||||
@update:clear="onClear"
|
||||
/>
|
||||
<v-list class="pa-0" density="compact" v-model:selected="selected" mandatory>
|
||||
@@ -198,7 +215,10 @@ async function onDeleteBatch() {
|
||||
</template>
|
||||
|
||||
<template #append>
|
||||
<div class="d-flex ga-4 align-center">
|
||||
<issue-stats />
|
||||
<v-btn density="compact" icon="mdi-dots-horizontal"></v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
|
||||
Reference in new issue
Block a user