diff --git a/src/components/quick-actions/quick-actions.vue b/src/components/quick-actions/quick-actions.vue index a6a5d47ce..18b24963e 100644 --- a/src/components/quick-actions/quick-actions.vue +++ b/src/components/quick-actions/quick-actions.vue @@ -24,7 +24,7 @@ {{ hintText }} - +
@@ -44,7 +44,18 @@ @keyup.prevent.enter="doAction(r.type, i)" @keyup.prevent.esc="searchInput?.focus()" > - {{ i.title }} + + +
@@ -66,6 +77,8 @@ import ProjectModel from '@/models/project' import BaseButton from '@/components/base/BaseButton.vue' import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue' +import XLabel from '@/components/tasks/partials/label.vue' +import SingleTaskInlineReadonly from '@/components/tasks/partials/singleTaskInlineReadonly.vue' import {useBaseStore} from '@/stores/base' import {useProjectStore} from '@/stores/projects' @@ -97,6 +110,7 @@ enum ACTION_TYPE { TASK = 'task', PROJECT = 'project', TEAM = 'team', + LABELS = 'labels', } enum COMMAND_TYPE { @@ -134,24 +148,38 @@ function closeQuickActions() { } const foundProjects = computed(() => { - const { project } = parsedQuery.value - if ( - searchMode.value === SEARCH_MODE.ALL || - searchMode.value === SEARCH_MODE.PROJECTS || - project === null - ) { + const {project, text, labels, assignees} = parsedQuery.value + + if (project !== null) { + return projectStore.searchProject(project ?? text) + .filter(p => Boolean(p)) + } + + if (labels.length > 0 || assignees.length > 0) { return [] } - const history = getHistory() - const allProjects = [ - ...new Set([ - ...history.map((l) => projectStore.projects[l.id]), - ...projectStore.searchProject(project), - ]), - ] + if (text === '') { + const history = getHistory() + return history.map((p) => projectStore.projects[p.id]) + .filter(p => Boolean(p)) + } - return allProjects.filter(l => Boolean(l)) + return projectStore.searchProject(project ?? text) + .filter(p => Boolean(p)) +}) + +const foundLabels = computed(() => { + const {labels, text} = parsedQuery.value + if (text === '' && labels.length === 0) { + return [] + } + + if (labels.length > 0) { + return labelStore.filterLabelsByQuery([], labels[0]) + } + + return labelStore.filterLabelsByQuery([], text) }) // FIXME: use fuzzysearch @@ -172,15 +200,20 @@ const results = computed(() => { title: t('quickActions.commands'), items: foundCommands.value, }, + { + type: ACTION_TYPE.PROJECT, + title: t('quickActions.projects'), + items: foundProjects.value, + }, { type: ACTION_TYPE.TASK, title: t('quickActions.tasks'), items: foundTasks.value, }, { - type: ACTION_TYPE.PROJECT, - title: t('quickActions.projects'), - items: foundProjects.value, + type: ACTION_TYPE.LABELS, + title: t('quickActions.labels'), + items: foundLabels.value, }, { type: ACTION_TYPE.TEAM, @@ -190,7 +223,7 @@ const results = computed(() => { ].filter((i) => i.items.length > 0) }) -const loading = computed(() => +const loading = computed(() => taskService.loading || projectStore.isLoading || teamService.loading, @@ -262,10 +295,12 @@ const searchMode = computed(() => { if (query.value === '') { return SEARCH_MODE.ALL } - const { text, project, labels, assignees } = parsedQuery.value + + const {text, project, labels, assignees} = parsedQuery.value if (assignees.length === 0 && text !== '') { return SEARCH_MODE.TASKS } + if ( assignees.length === 0 && project !== null && @@ -274,6 +309,7 @@ const searchMode = computed(() => { ) { return SEARCH_MODE.PROJECTS } + if ( assignees.length > 0 && project === null && @@ -282,6 +318,7 @@ const searchMode = computed(() => { ) { return SEARCH_MODE.TEAMS } + return SEARCH_MODE.ALL }) @@ -292,12 +329,12 @@ const isNewTaskCommand = computed(() => ( const taskSearchTimeout = ref | null>(null) -type Filter = {by: string, value: string | number, comparator: string} +type Filter = { by: string, value: string | number, comparator: string } function filtersToParams(filters: Filter[]) { - const filter_by : Filter['by'][] = [] - const filter_value : Filter['value'][] = [] - const filter_comparator : Filter['comparator'][] = [] + const filter_by: Filter['by'][] = [] + const filter_value: Filter['value'][] = [] + const filter_comparator: Filter['comparator'][] = [] filters.forEach(({by, value, comparator}) => { filter_by.push(by) @@ -315,7 +352,8 @@ function filtersToParams(filters: Filter[]) { function searchTasks() { if ( searchMode.value !== SEARCH_MODE.ALL && - searchMode.value !== SEARCH_MODE.TASKS + searchMode.value !== SEARCH_MODE.TASKS && + searchMode.value !== SEARCH_MODE.PROJECTS ) { foundTasks.value = [] return @@ -330,7 +368,7 @@ function searchTasks() { taskSearchTimeout.value = null } - const { text, project: projectName, labels } = parsedQuery.value + const {text, project: projectName, labels} = parsedQuery.value const filters: Filter[] = [] @@ -349,8 +387,9 @@ function searchTasks() { if (projectName !== null) { const project = projectStore.findProjectByExactname(projectName) + console.log({project}) if (project !== null) { - addFilter('projectId', project.id, 'equals') + addFilter('project_id', project.id, 'equals') } } @@ -361,19 +400,16 @@ function searchTasks() { } } - const params = { - s: text, - ...filtersToParams(filters), - } + const params = { + s: text, + sort_by: 'done', + ...filtersToParams(filters), + } taskSearchTimeout.value = setTimeout(async () => { - const r = await taskService.getAll({}, params) as DoAction[] + const r = await taskService.getAll({}, params) as DoAction[] foundTasks.value = r.map((t) => { t.type = ACTION_TYPE.TASK - const project = projectStore.projects[t.projectId] - if (project !== null) { - t.title = `${t.title} (${project.title})` - } return t }) }, 150) @@ -396,10 +432,10 @@ function searchTeams() { clearTimeout(teamSearchTimeout.value) teamSearchTimeout.value = null } - const { assignees } = parsedQuery.value + const {assignees} = parsedQuery.value teamSearchTimeout.value = setTimeout(async () => { const teamSearchPromises = assignees.map((t) => - teamService.getAll({}, { s: t }), + teamService.getAll({}, {s: t}), ) const teamsResult = await Promise.all(teamSearchPromises) foundTeams.value = teamsResult.flat().map((team) => { @@ -422,21 +458,21 @@ async function doAction(type: ACTION_TYPE, item: DoAction) { closeQuickActions() await router.push({ name: 'project.index', - params: { projectId: (item as DoAction).id }, + params: {projectId: (item as DoAction).id}, }) break case ACTION_TYPE.TASK: closeQuickActions() await router.push({ name: 'task.detail', - params: { id: (item as DoAction).id }, + params: {id: (item as DoAction).id}, }) break case ACTION_TYPE.TEAM: closeQuickActions() await router.push({ name: 'teams.edit', - params: { id: (item as DoAction).id }, + params: {id: (item as DoAction).id}, }) break case ACTION_TYPE.CMD: @@ -444,6 +480,11 @@ async function doAction(type: ACTION_TYPE, item: DoAction) { selectedCmd.value = item as DoAction searchInput.value?.focus() break + case ACTION_TYPE.LABELS: + query.value = '*' + item.title + searchInput.value?.focus() + searchTasks() + break } } @@ -470,8 +511,8 @@ async function newTask() { title: query.value, projectId: currentProject.value.id, }) - success({ message: t('task.createSuccess') }) - await router.push({ name: 'task.detail', params: { id: task.id } }) + success({message: t('task.createSuccess')}) + await router.push({name: 'task.detail', params: {id: task.id}}) } async function newProject() { @@ -481,17 +522,17 @@ async function newProject() { await projectStore.createProject(new ProjectModel({ title: query.value, })) - success({ message: t('project.create.createdSuccess')}) + success({message: t('project.create.createdSuccess')}) } async function newTeam() { - const newTeam = new TeamModel({ name: query.value }) + const newTeam = new TeamModel({name: query.value}) const team = await teamService.create(newTeam) await router.push({ name: 'teams.edit', - params: { id: team.id }, + params: {id: team.id}, }) - success({ message: t('team.create.success') }) + success({message: t('team.create.success')}) } type BaseButtonInstance = InstanceType @@ -502,7 +543,7 @@ function setResultRefs(el: Element | ComponentPublicInstance | null, index: numb resultRefs.value[index] = [] } - resultRefs.value[index][key] = el as (BaseButtonInstance | null) + resultRefs.value[index][key] = el as (BaseButtonInstance | null) } function select(parentIndex: number, index: number) { @@ -547,7 +588,7 @@ function reset() { \ No newline at end of file diff --git a/src/components/tasks/partials/label.vue b/src/components/tasks/partials/label.vue new file mode 100644 index 000000000..d8f515a0e --- /dev/null +++ b/src/components/tasks/partials/label.vue @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/src/components/tasks/partials/labels.vue b/src/components/tasks/partials/labels.vue index 8727695a9..e80d06798 100644 --- a/src/components/tasks/partials/labels.vue +++ b/src/components/tasks/partials/labels.vue @@ -1,12 +1,10 @@ @@ -14,6 +12,8 @@ import type {PropType} from 'vue' import type {ILabel} from '@/modelTypes/ILabel' +import XLabel from '@/components/tasks/partials/label.vue' + defineProps({ labels: { type: Array as PropType, @@ -26,10 +26,4 @@ defineProps({ .label-wrapper { display: inline; } - -.tag { - & + & { - margin-left: 0.5rem; - } -} \ No newline at end of file diff --git a/src/components/tasks/partials/quick-add-magic.vue b/src/components/tasks/partials/quick-add-magic.vue index 30818a0ff..9595fb178 100644 --- a/src/components/tasks/partials/quick-add-magic.vue +++ b/src/components/tasks/partials/quick-add-magic.vue @@ -108,7 +108,7 @@ const visible = ref(false) const mode = computed(() => authStore.settings.frontendSettings.quickAddMagicMode) defineProps<{ - highlightHintIcon: boolean, + highlightHintIcon?: boolean, }>() const prefixes = computed(() => PREFIXES[mode.value]) diff --git a/src/components/tasks/partials/singleTaskInlineReadonly.vue b/src/components/tasks/partials/singleTaskInlineReadonly.vue new file mode 100644 index 000000000..b20c0a765 --- /dev/null +++ b/src/components/tasks/partials/singleTaskInlineReadonly.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/src/i18n/lang/en.json b/src/i18n/lang/en.json index bd87f8f1a..be6bf4ed6 100644 --- a/src/i18n/lang/en.json +++ b/src/i18n/lang/en.json @@ -905,6 +905,7 @@ "tasks": "Tasks", "projects": "Projects", "teams": "Teams", + "labels": "Labels", "newProject": "Enter the title of the new project…", "newTask": "Enter the title of the new task…", "newTeam": "Enter the name of the new team…", diff --git a/src/stores/projects.ts b/src/stores/projects.ts index b3ce6d889..a18e14956 100644 --- a/src/stores/projects.ts +++ b/src/stores/projects.ts @@ -19,7 +19,7 @@ import {success} from '@/message' import {useBaseStore} from '@/stores/base' import {getSavedFilterIdFromProjectId} from '@/services/savedFilter' -const {remove, search, update} = createNewIndexer('projects', ['title', 'description']) +const {add, remove, search, update} = createNewIndexer('projects', ['title', 'description']) export interface ProjectState { [id: IProject['id']]: IProject @@ -174,6 +174,7 @@ export const useProjectStore = defineStore('project', () => { const loadedProjects = await projectService.getAll({}, {is_archived: true}) as IProject[] projects.value = {} setProjects(loadedProjects) + loadedProjects.forEach(p => add(p)) return loadedProjects } finally {