diff --git a/src/App.vue b/src/App.vue index f4a8614..1b3d301 100644 --- a/src/App.vue +++ b/src/App.vue @@ -4,6 +4,7 @@ import { storeToRefs } from 'pinia' import { computed, onMounted, watch } from 'vue' import { useRoute } from 'vue-router' import { useStatusStore } from '@/stores/status.ts' +import { useIssueTypeStore } from '@/stores/issue-type.ts' import { useIssueStore } from '@/stores/issue.ts' import { useProjectStore } from '@/stores/project.ts' import { authClient } from '@/lib/auth-client.ts' @@ -13,6 +14,7 @@ import SnackbarQueue from '@/components/SnackbarQueue.vue' const route = useRoute() const issueStore = useIssueStore() const statusStore = useStatusStore() +const issueTypeStore = useIssueTypeStore() const projectStore = useProjectStore() const session = authClient.useSession() const push = usePushNotifications() @@ -60,7 +62,7 @@ const layout = computed(() => { }) onMounted(async () => { - await statusStore.list() + await Promise.all([statusStore.list(), issueTypeStore.list()]) await issueStore.getStat(currentProjectId.value) }) diff --git a/src/api/issue-type.api.ts b/src/api/issue-type.api.ts new file mode 100644 index 0000000..9f79e2d --- /dev/null +++ b/src/api/issue-type.api.ts @@ -0,0 +1,13 @@ +import { apiClient } from '@/lib/api-client.ts' + +export interface IssueType { + name: string + alias: 'TASK' | 'BUG' | 'FEATURE' + color: string +} + +// CLIENT REQUEST + +export async function issueTypesList() { + return apiClient.get('/api/issue-types') +} diff --git a/src/api/issue.api.ts b/src/api/issue.api.ts index 255b58c..e2f52a4 100644 --- a/src/api/issue.api.ts +++ b/src/api/issue.api.ts @@ -1,5 +1,6 @@ import { apiClient, toQuery } from '@/lib/api-client.ts' import type { User } from '@/api/user.api.ts' +import type { IssueType } from '@/api/issue-type.api.ts' export interface Issue { id: number @@ -7,6 +8,7 @@ export interface Issue { title: string content: string status: 'OPEN' | 'IN_PROGRESS' | 'DONE' | 'CANCELLED' + type: IssueType['alias'] priority: 0 | 1 | 2 | 3 | 4 assigneeId: string reporterId: string @@ -29,6 +31,7 @@ export interface CreateIssueInput { projectId: string content?: string status?: Issue['status'] + type?: Issue['type'] priority?: Issue['priority'] assignee?: string dueDate?: string | null diff --git a/src/components/IssueDetails.vue b/src/components/IssueDetails.vue index 661923b..f86fafd 100644 --- a/src/components/IssueDetails.vue +++ b/src/components/IssueDetails.vue @@ -15,6 +15,7 @@ import SplitHandle from '@/components/SplitHandle.vue' import { useSplitPane } from '@/composables/useSplitPane.ts' import FileUpload from '@/components/FileUpload.vue' import PriorityMenu from '@/components/PriorityMenu.vue' +import IssueType from '@/components/IssueType.vue' interface Props { ckey: string @@ -22,6 +23,7 @@ interface Props { const emit = defineEmits<{ statusChanged: [issue: Issue] + typeChanged: [issue: Issue] assigneeChanged: [issue: Issue] priorityChanged: [issue: Issue] titleChanged: [issue: Issue] @@ -77,6 +79,16 @@ const onContentChanged = async (content: string) => { if (updated) issue.value = updated } +const onTypeChanged = async (type: Issue['type']) => { + if (!issue.value) return + + const updated = await issueStore.update(issue.value.id, { type }) + if (updated) { + issue.value = updated + emit('typeChanged', updated) + } +} + const onStatusChanged = async (status: Issue['status']) => { if (!issue.value) return @@ -128,6 +140,9 @@ defineExpose({
Status:
+
Type:
+ +
Priority:
diff --git a/src/components/IssueType.vue b/src/components/IssueType.vue new file mode 100644 index 0000000..c5c7adc --- /dev/null +++ b/src/components/IssueType.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/src/components/IssuesListElement.vue b/src/components/IssuesListElement.vue index 242dcfd..8738aca 100644 --- a/src/components/IssuesListElement.vue +++ b/src/components/IssuesListElement.vue @@ -5,6 +5,7 @@ import { useIssueStore } from '@/stores/issue.ts' import type { Issue } from '@/api/issue.api.ts' import AssigneeMenu from '@/components/AssigneeMenu.vue' import { useIssueListOptions } from '@/composables/useIssueListOptions.ts' +import IssueType from '@/components/IssueType.vue' interface Props { issue: Issue @@ -14,6 +15,7 @@ interface Props { const { issue, selected } = defineProps() const emit = defineEmits<{ statusChanged: [issue: Issue] + typeChanged: [issue: Issue] priorityChanged: [issue: Issue] assigneeChanged: [issue: Issue] toggleSelect: [id: number] @@ -27,6 +29,11 @@ const onStatusChanged = async (status: Issue['status']) => { if (updated) emit('statusChanged', updated) } +const onTypeChanged = async (type: Issue['type']) => { + const updated = await issueStore.update(issue.id, { type }) + if (updated) emit('typeChanged', updated) +} + const onPriorityChanged = async (priority: Issue['priority']) => { const updated = await issueStore.update(issue.id, { priority }) if (updated) emit('priorityChanged', updated) @@ -49,7 +56,11 @@ const onAssigneeChanged = async (assignee: string) => { icon="mdi-alert-box" color="error" /> -
+
{{ issue.title }}
{ :model-value="selected" @update:model-value="emit('toggleSelect', issue.id)" /> +
diff --git a/src/stores/issue-type.ts b/src/stores/issue-type.ts new file mode 100644 index 0000000..7d7d2dc --- /dev/null +++ b/src/stores/issue-type.ts @@ -0,0 +1,13 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { type IssueType, issueTypesList } from '@/api/issue-type.api.ts' + +export const useIssueTypeStore = defineStore('issueType', () => { + const issueTypes = ref() + + async function list() { + issueTypes.value = await issueTypesList() + } + + return { issueTypes, list } +}) diff --git a/src/views/Issues/IssuesListView.vue b/src/views/Issues/IssuesListView.vue index 7150647..0601bd4 100644 --- a/src/views/Issues/IssuesListView.vue +++ b/src/views/Issues/IssuesListView.vue @@ -248,6 +248,7 @@ async function onDeleteBatch() { :issue="el" :selected="batch.includes(el.id)" @status-changed="onIssueUpdated" + @type-changed="onIssueUpdated" @priority-changed="onIssueUpdated" @assignee-changed="onIssueUpdated" @toggle-select="toggleBatchSelect" @@ -306,6 +307,7 @@ async function onDeleteBatch() { ref="issueDetailsRef" :ckey="selected[0]" @status-changed="onIssueUpdated" + @type-changed="onIssueUpdated" @assignee-changed="onIssueUpdated" @priority-changed="onIssueUpdated" @title-changed="onIssueUpdated"