home page

This commit is contained in:
vadim committed 2026-10-06 18:11:20 +03:00
1 parent adf182a914
commit fcc438e5fe
5 files changed
+35 -20

No files matched your search

+2 -2
View File
@@ -53,7 +53,7 @@ export async function itemsList(params: {
page: number page: number
limit: number limit: number
text?: string | null text?: string | null
status?: string | null status?: string | string[] | null
assigneeId?: string | null assigneeId?: string | null
projectId?: string | null projectId?: string | null
}) { }) {
@@ -63,7 +63,7 @@ export async function itemsList(params: {
projectId: params.projectId, projectId: params.projectId,
text: params.text, text: params.text,
assigneeId: params.assigneeId, assigneeId: params.assigneeId,
status: params.status, status: Array.isArray(params.status) ? params.status.join(',') : params.status,
}) })
return apiClient.get<{ list: Issue[]; meta: Meta }>(`/api/issues/list${query}`) return apiClient.get<{ list: Issue[]; meta: Meta }>(`/api/issues/list${query}`)
} }
+7 -5
View File
@@ -3,9 +3,12 @@ import { reactive, ref } from 'vue'
import { useIssueStore } from '@/stores/issue.ts' import { useIssueStore } from '@/stores/issue.ts'
import { useProjectStore } from '@/stores/project.ts' import { useProjectStore } from '@/stores/project.ts'
import type { Issue } from '@/api/issue.api.ts' import type { Issue } from '@/api/issue.api.ts'
import { storeToRefs } from 'pinia'
const issueStore = useIssueStore() const issueStore = useIssueStore()
const projectStore = useProjectStore() const projectStore = useProjectStore()
const { current } = storeToRefs(projectStore)
const emit = defineEmits<{ created: [issue: Issue] }>() const emit = defineEmits<{ created: [issue: Issue] }>()
const showForm = ref(false) const showForm = ref(false)
@@ -15,10 +18,9 @@ const newIssue = reactive({
}) })
const onCreate = async () => { const onCreate = async () => {
const project = projectStore.current if (!current.value) return
if (!project) return
const created = await issueStore.create({ ...newIssue, projectId: project.id, key: project.key }) const created = await issueStore.create({ ...newIssue, projectId: current.value.id, key: current.value.key })
if (!created) return if (!created) return
emit('created', created) emit('created', created)
@@ -29,7 +31,7 @@ const onCreate = async () => {
</script> </script>
<template> <template>
<v-list-item v-if="showForm && projectStore.current"> <v-list-item v-if="showForm && current">
<template #prepend> <template #prepend>
<v-menu v-if="false"> <v-menu v-if="false">
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
@@ -58,7 +60,7 @@ const onCreate = async () => {
</div> </div>
</template> </template>
</v-list-item> </v-list-item>
<v-list-item v-else-if="showForm && !projectStore.current"> <v-list-item v-else-if="showForm && !current">
<div> <div>
<v-alert closable density="compact" @click:close="showForm = false"> <v-alert closable density="compact" @click:close="showForm = false">
Select or create project Select or create project
+4 -2
View File
@@ -3,6 +3,7 @@ import { computed } from 'vue'
import AssigneeFilter from '@/components/AssigneeFilter.vue' import AssigneeFilter from '@/components/AssigneeFilter.vue'
import TextFilter from '@/components/TextFilter.vue' import TextFilter from '@/components/TextFilter.vue'
import StatusFilter from '@/components/StatusFilter.vue' import StatusFilter from '@/components/StatusFilter.vue'
import ProjectMenu from '@/components/ProjectMenu.vue'
interface Props { interface Props {
text?: string | null text?: string | null
@@ -30,7 +31,8 @@ function onStatusChanged(alias: string) {
</script> </script>
<template> <template>
<div class="filter-grid ga-2 align-center"> <div class="filter-grid ga-4 align-center">
<project-menu />
<text-filter :model-value="text" @update:model-value="emit('update:text', $event)" /> <text-filter :model-value="text" @update:model-value="emit('update:text', $event)" />
<status-filter :status="status" @changed="onStatusChanged" /> <status-filter :status="status" @changed="onStatusChanged" />
<assignee-filter :selected="selectedAssignees" @changed="onAssigneeChanged" /> <assignee-filter :selected="selectedAssignees" @changed="onAssigneeChanged" />
@@ -41,6 +43,6 @@ function onStatusChanged(alias: string) {
<style scoped lang="scss"> <style scoped lang="scss">
.filter-grid { .filter-grid {
display: grid; display: grid;
grid-template-columns: 1fr auto auto auto; grid-template-columns: auto 1fr auto auto auto;
} }
</style> </style>
+1 -1
View File
@@ -28,7 +28,7 @@ export const useIssueStore = defineStore('item', () => {
page: number page: number
limit: number limit: number
text?: string | null text?: string | null
status?: string | null status?: string | string[] | null
assigneeId?: string | null assigneeId?: string | null
projectId?: string | null projectId?: string | null
}) { }) {
+21 -10
View File
@@ -30,32 +30,43 @@ const projectStore = useProjectStore()
const statusStore = useStatusStore() const statusStore = useStatusStore()
const { stats } = storeToRefs(issueStore) const { stats } = storeToRefs(issueStore)
const { projects, currentProjectId } = storeToRefs(projectStore) const { projects, current, currentProjectId } = storeToRefs(projectStore)
const currentUserId = computed(() => session.value.data?.user.id ?? null) const currentUserId = computed(() => session.value.data?.user.id ?? null)
const myIssues = ref<Issue[]>([]) const myIssues = ref<Issue[]>([])
const loadingMyIssues = ref(false) const loadingMyIssues = ref(false)
let myIssuesRequestId = 0
async function loadMyIssues(userId: string) { async function loadMyIssues(userId: string, projectId: string | null) {
// Ignore responses that arrive after the user or project has changed again
const requestId = ++myIssuesRequestId
loadingMyIssues.value = true loadingMyIssues.value = true
try { try {
const res = await issueStore.list({ page: 1, limit: 50, assigneeId: userId }) const res = await issueStore.list({ page: 1, limit: 50, assigneeId: userId, projectId, status: ACTIVE_STATUSES })
myIssues.value = res.list.filter((issue) => ACTIVE_STATUSES.includes(issue.status)) if (requestId !== myIssuesRequestId) return
myIssues.value = res.list
} finally { } finally {
loadingMyIssues.value = false if (requestId === myIssuesRequestId) loadingMyIssues.value = false
} }
} }
watch( watch(
currentUserId, [currentUserId, currentProjectId],
(id) => { ([userId, projectId]) => {
if (id) void loadMyIssues(id) if (userId) void loadMyIssues(userId, projectId)
else myIssues.value = [] else myIssues.value = []
}, },
{ immediate: true }, { immediate: true },
) )
const myIssuesTitle = computed(() => (current.value ? `My issues · ${current.value.key}` : 'My issues'))
const myIssuesLink = computed(() => ({
path: '/issues',
query: currentUserId.value ? { assignee: currentUserId.value } : {},
}))
watch( watch(
currentProjectId, currentProjectId,
(id) => { (id) => {
@@ -134,8 +145,8 @@ function onClickCard(status?: string) {
<v-col cols="12" md="8"> <v-col cols="12" md="8">
<v-card border> <v-card border>
<v-card-title class="d-flex align-center justify-space-between"> <v-card-title class="d-flex align-center justify-space-between">
<span>My issues</span> <span>{{ myIssuesTitle }}</span>
<v-btn size="small" variant="text" to="/issues">All issues</v-btn> <v-btn size="small" variant="text" :to="myIssuesLink">View all mine</v-btn>
</v-card-title> </v-card-title>
<v-divider /> <v-divider />
<v-list v-if="sortedMyIssues.length" density="compact"> <v-list v-if="sortedMyIssues.length" density="compact">