diff --git a/src/api/history.api.ts b/src/api/history.api.ts index 666bb9d..cccd51c 100644 --- a/src/api/history.api.ts +++ b/src/api/history.api.ts @@ -2,9 +2,10 @@ import { apiClient } from '@/lib/api-client.ts' import type { User } from '@/api/user.api.ts' /** created — a pseudo-field: the issue creation record, both values are null */ -export type IssueChangeField = 'created' | 'title' | 'content' | 'status' | 'priority' | 'assignee' | 'dueDate' | 'type' +export type IssueChangeField = + 'created' | 'title' | 'content' | 'status' | 'priority' | 'assignee' | 'dueDate' | 'type' | 'tag' -/** Values are serialized: priority — number, dueDate — ISO date, assignee — user id */ +/** Values are serialized: priority — number, dueDate — ISO date, assignee — user id, tag — one change per tag name */ export interface IssueFieldChange { field: IssueChangeField oldValue: string | null diff --git a/src/api/issue.api.ts b/src/api/issue.api.ts index e2f52a4..dc88e61 100644 --- a/src/api/issue.api.ts +++ b/src/api/issue.api.ts @@ -2,9 +2,16 @@ 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 IssueTag { + id: string + name: string + color: string +} + export interface Issue { id: number key: string + projectId: string title: string content: string status: 'OPEN' | 'IN_PROGRESS' | 'DONE' | 'CANCELLED' @@ -14,6 +21,7 @@ export interface Issue { reporterId: string assignee?: User reporter?: User + tags: IssueTag[] dueDate: string | null createdAt: string updatedAt: string @@ -35,6 +43,8 @@ export interface CreateIssueInput { priority?: Issue['priority'] assignee?: string dueDate?: string | null + /** Names, the full new list; names missing from the project's tags are created */ + tags?: string[] } export type UpdateIssueInput = Partial diff --git a/src/api/tag.api.ts b/src/api/tag.api.ts new file mode 100644 index 0000000..be26377 --- /dev/null +++ b/src/api/tag.api.ts @@ -0,0 +1,21 @@ +import { apiClient, toQuery } from '@/lib/api-client.ts' + +export const TAG_NAME_MAX_LENGTH = 50 +/** Upper bound of the backend's list limit */ +export const TAGS_LIST_MAX_LIMIT = 500 + +export interface Tag { + id: string + projectId: string + name: string + color: string + createdAt: string + updatedAt: string +} + +// CLIENT REQUEST + +/** Sorted by name; q — case-insensitive substring of the name */ +export async function tagsList(projectId: string, params: { q?: string; limit?: number } = {}) { + return apiClient.get(`/api/projects/${projectId}/tags${toQuery(params)}`) +} diff --git a/src/components/HistoryList.vue b/src/components/HistoryList.vue index f417063..058d958 100644 --- a/src/components/HistoryList.vue +++ b/src/components/HistoryList.vue @@ -21,6 +21,7 @@ const FIELD_LABELS: Record = { assignee: 'Assignee', dueDate: 'Due Date', type: 'Type', + tag: 'Tag', } const PRIORITY_LABELS: Record = { @@ -145,7 +146,7 @@ function toggle(key: string) { {{ item.time }} -
+
{{ FIELD_LABELS[change.field] }}:
diff --git a/src/components/IssueDetails.vue b/src/components/IssueDetails.vue index 7cb99f2..0ac3aee 100644 --- a/src/components/IssueDetails.vue +++ b/src/components/IssueDetails.vue @@ -1,6 +1,7 @@ + + + + diff --git a/src/components/TagsSelector.vue b/src/components/TagsSelector.vue new file mode 100644 index 0000000..9550aee --- /dev/null +++ b/src/components/TagsSelector.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/src/router/settings.ts b/src/router/settings.ts index 26dedf4..145e9bb 100644 --- a/src/router/settings.ts +++ b/src/router/settings.ts @@ -2,6 +2,7 @@ import type { RouteRecordRaw } from 'vue-router' import ProjectsListView from '@/views/project/ProjectsListView.vue' import SettingsView from '@/views/settings/SettingsView.vue' import GeneralSettingsView from '@/views/settings/GeneralSettingsView.vue' +import TagsSettingsView from '@/views/settings/TagsSettingsView.vue' export const settingsRoutes: RouteRecordRaw = { path: '/settings', @@ -21,5 +22,11 @@ export const settingsRoutes: RouteRecordRaw = { component: ProjectsListView, meta: { breadcrumb: 'Projects' }, }, + { + path: 'tags', + name: 'settings-tags', + component: TagsSettingsView, + meta: { breadcrumb: 'Tags' }, + }, ], } diff --git a/src/stores/issue.ts b/src/stores/issue.ts index 067df48..29fa0fc 100644 --- a/src/stores/issue.ts +++ b/src/stores/issue.ts @@ -127,7 +127,7 @@ export const useIssueStore = defineStore('item', () => { error.value = null } - return { stats, dailyStats, list, calendar, getByKey, create, update, remove, getStat, getDailyStat, reset } + return { stats, dailyStats, error, list, calendar, getByKey, create, update, remove, getStat, getDailyStat, reset } }) /** Local YYYY-MM-DD `days` days before today */ diff --git a/src/views/settings/SettingsView.vue b/src/views/settings/SettingsView.vue index 789c863..5cfd6f8 100644 --- a/src/views/settings/SettingsView.vue +++ b/src/views/settings/SettingsView.vue @@ -12,6 +12,7 @@ const tab = ref(0) General Projects + Tags
diff --git a/src/views/settings/TagsSettingsView.vue b/src/views/settings/TagsSettingsView.vue new file mode 100644 index 0000000..edce394 --- /dev/null +++ b/src/views/settings/TagsSettingsView.vue @@ -0,0 +1,62 @@ + + + + +