Compare commits

...

10 Commits

Author SHA1 Message Date
kolaente ca90b9e5d4
fix 2025-11-15 18:10:44 +01:00
kolaente 1f72999313
fix: remove disabled 2025-11-15 18:03:14 +01:00
kolaente 7c954acbae
imrpve 2025-11-15 18:01:06 +01:00
copilot-swe-agent[bot] 17bcb51ceb refactor: simplify implementation - make labels clickable only on ListLabels page
- Revert Label.vue to original state (no clickable prop)
- Revert Labels.vue, QuickActions.vue, FilterCommandsList.vue to original state
- Implement clickable labels inline in ListLabels.vue using RouterLink
- Remove dependency on XLabel component in ListLabels
- Keep edit button behavior (delete icon opens edit form)
- Keep ShowTasks.vue and Home.vue filtering functionality unchanged

Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-15 16:39:29 +00:00
copilot-swe-agent[bot] ae19962f59 fix: implement review feedback on ListLabels and ShowTasks
- Move edit button to replace delete button in ListLabels
- Remove separate delete button (edit button now uses delete icon styling)
- Remove Icon import and unused styles from ListLabels
- Add tooltip to clear label filter button
- Add translation key for clear filter tooltip
- Remove bold font-weight from filter label text

Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-15 16:27:58 +00:00
copilot-swe-agent[bot] 42fb36e409 fix: address review feedback
- Fix Icon import path (Icon.vue -> Icon)
- Update translation string to remove colon
- Make labels clickable ONLY on ListLabels page
- Remove clickable from Labels.vue (labels on tasks are not clickable)
- Update ListLabels to use XLabel component with clickable={true}
- Add edit button with pen icon next to labels on ListLabels page

Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-15 16:17:37 +00:00
copilot-swe-agent[bot] 31a101e352 refactor: implement review feedback
- Change default clickable prop to false (simpler code)
- Update Labels.vue to explicitly set clickable=true
- Remove clickable=false from QuickActions and FilterCommandsList (now default)
- Add {label} placeholder to translation string
- Use i18n-t component to render label within translation
- Move router.push from ShowTasks to Home via event emission
- Add clearLabelFilter event handler in Home.vue

Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-15 16:06:41 +00:00
copilot-swe-agent[bot] 8013f0e51a fix: prevent labels from being clickable in selection contexts
- Set clickable=false for labels in QuickActions modal
- Set clickable=false for labels in FilterCommandsList autocomplete
- This prevents navigation conflicts when labels are shown in interactive components

Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-15 15:38:27 +00:00
copilot-swe-agent[bot] c16ca20db5 feat: add clickable labels with filtering support
- Made labels clickable in Label.vue component
- Added label filtering to ShowTasks component via query parameters
- Updated Home.vue to pass label query parameters to ShowTasks
- Added visual indicator showing active label filter with clear button
- Added translation for "Filtering by label" message

Co-authored-by: kolaente <13721712+kolaente@users.noreply.github.com>
2025-11-15 15:36:23 +00:00
copilot-swe-agent[bot] 8bb02408c7 Initial plan 2025-11-15 15:27:55 +00:00
4 changed files with 127 additions and 20 deletions

View File

@ -807,7 +807,9 @@
"overdue": "Show overdue tasks",
"fromuntil": "Tasks from {from} until {until}",
"select": "Select a date range",
"noTasks": "Nothing to do — Have a nice day!"
"noTasks": "Nothing to do — Have a nice day!",
"filterByLabel": "Filtering by label {label}",
"clearLabelFilter": "Clear label filter"
},
"detail": {
"chooseDueDate": "Click here to set a due date",

View File

@ -38,14 +38,17 @@
<ShowTasks
v-if="projectStore.hasProjects"
:key="showTasksKey"
:label-ids="labelIds"
class="show-tasks"
@tasksLoaded="tasksLoaded = true"
@clearLabelFilter="handleClearLabelFilter"
/>
</div>
</template>
<script lang="ts" setup>
import {ref, computed} from 'vue'
import {useRoute, useRouter} from 'vue-router'
import Message from '@/components/misc/Message.vue'
import ShowTasks from '@/views/tasks/ShowTasks.vue'
@ -65,6 +68,8 @@ const salutation = useDaytimeSalutation()
const authStore = useAuthStore()
const projectStore = useProjectStore()
const route = useRoute()
const router = useRouter()
const projectHistory = computed(() => {
// If we don't check this, it tries to load the project background right after logging out
@ -81,6 +86,15 @@ const tasksLoaded = ref(false)
const deletionScheduledAt = computed(() => parseDateOrNull(authStore.info?.deletionScheduledAt))
// Extract label IDs from query parameter
const labelIds = computed(() => {
const labelsParam = route.query.labels
if (!labelsParam) {
return undefined
}
return Array.isArray(labelsParam) ? labelsParam : [labelsParam]
})
// This is to reload the tasks list after adding a new task through the global task add.
// FIXME: Should use pinia (somehow?)
const showTasksKey = ref(0)
@ -88,6 +102,15 @@ const showTasksKey = ref(0)
function updateTaskKey() {
showTasksKey.value++
}
function handleClearLabelFilter() {
const query = {...route.query}
delete query.labels
router.push({
name: route.name as string,
query,
})
}
</script>
<style scoped lang="scss">

View File

@ -29,32 +29,22 @@
<div class="columns">
<div class="labels-list column">
<span
<RouterLink
v-for="label in labelStore.labelsArray"
:key="label.id"
:class="{'disabled': userInfo.id !== label.createdBy.id}"
:to="{name: 'home', query: {labels: label.id.toString()}}"
:style="getLabelStyles(label)"
class="tag"
>
<span
v-if="userInfo.id !== label.createdBy.id"
v-tooltip.bottom="$t('label.edit.forbidden')"
>
{{ label.title }}
</span>
<BaseButton
v-else
:style="{'color': label.textColor}"
@click="editLabel(label)"
>
{{ label.title }}
</BaseButton>
<span>{{ label.title }}</span>
<BaseButton
v-if="userInfo.id === label.createdBy.id"
class="delete is-small"
@click="showDeleteDialoge(label)"
/>
</span>
class="label-edit-button is-small"
@click.stop.prevent="editLabel(label)"
>
<Icon icon="pen" class="icon"/>
</BaseButton>
</RouterLink>
</div>
<div
v-if="isLabelEdit"
@ -212,3 +202,21 @@ function showDeleteDialoge(label: ILabel) {
showDeleteModal.value = true
}
</script>
<style lang="scss" scoped>
.label-edit-button {
border-radius: 100%;
background-color: rgba(0,0,0,0.2);
inline-size: 1rem;
block-size: 1rem;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff; // always white
margin-inline-start: .25rem;
.icon {
block-size: .5rem;
}
}
</style>

View File

@ -6,6 +6,31 @@
<h3 class="mbe-2 title">
{{ pageTitle }}
</h3>
<Message
v-if="filteredLabels.length > 0"
class="label-filter-info mbe-2"
>
<i18n-t
keypath="task.show.filterByLabel"
tag="span"
class="filter-label-text"
>
<template #label>
<XLabel
v-for="label in filteredLabels"
:key="label.id"
:label="label"
/>
</template>
</i18n-t>
<BaseButton
v-tooltip="$t('task.show.clearLabelFilter')"
class="clear-filter-button"
@click="clearLabelFilter"
>
<Icon icon="times" />
</BaseButton>
</Message>
<p
v-if="!showAll"
class="show-tasks-options"
@ -76,15 +101,20 @@ import {useI18n} from 'vue-i18n'
import {formatDate} from '@/helpers/time/formatDate'
import {setTitle} from '@/helpers/setTitle'
import BaseButton from '@/components/base/BaseButton.vue'
import Icon from '@/components/misc/Icon'
import Message from '@/components/misc/Message.vue'
import FancyCheckbox from '@/components/input/FancyCheckbox.vue'
import SingleTaskInProject from '@/components/tasks/partials/SingleTaskInProject.vue'
import DatepickerWithRange from '@/components/date/DatepickerWithRange.vue'
import XLabel from '@/components/tasks/partials/Label.vue'
import {DATE_RANGES} from '@/components/date/dateRanges'
import LlamaCool from '@/assets/llama-cool.svg?component'
import type {ITask} from '@/modelTypes/ITask'
import {useAuthStore} from '@/stores/auth'
import {useTaskStore} from '@/stores/tasks'
import {useProjectStore} from '@/stores/projects'
import {useLabelStore} from '@/stores/labels'
import type {TaskFilterParams} from '@/services/taskCollection'
import TaskCollectionService from '@/services/taskCollection'
@ -93,20 +123,24 @@ const props = withDefaults(defineProps<{
dateTo?: Date | string,
showNulls?: boolean,
showOverdue?: boolean,
labelIds?: string[],
}>(), {
showNulls: false,
showOverdue: false,
dateFrom: undefined,
dateTo: undefined,
labelIds: undefined,
})
const emit = defineEmits<{
'tasksLoaded': true,
'clearLabelFilter': void,
}>()
const authStore = useAuthStore()
const taskStore = useTaskStore()
const projectStore = useProjectStore()
const labelStore = useLabelStore()
const route = useRoute()
const router = useRouter()
@ -120,6 +154,15 @@ setTimeout(() => showNothingToDo.value = true, 100)
const showAll = computed(() => typeof props.dateFrom === 'undefined' || typeof props.dateTo === 'undefined')
const filteredLabels = computed(() => {
if (!props.labelIds || props.labelIds.length === 0) {
return []
}
return props.labelIds
.map(id => labelStore.getLabelById(Number(id)))
.filter(label => label !== null && label !== undefined)
})
const pageTitle = computed(() => {
// We need to define "key" because it is the first parameter in the array and we need the second
const predefinedRange = Object.entries(DATE_RANGES)
@ -177,6 +220,10 @@ function setShowNulls(show: boolean) {
})
}
function clearLabelFilter() {
emit('clearLabelFilter')
}
async function loadPendingTasks(from: Date|string, to: Date|string) {
// FIXME: HACK! This should never happen.
// Since this route is authentication only, users would get an error message if they access the page unauthenticated.
@ -207,6 +254,12 @@ async function loadPendingTasks(from: Date|string, to: Date|string) {
}
}
// Add label filtering
if (props.labelIds && props.labelIds.length > 0) {
const labelFilter = `labels in ${props.labelIds.join(', ')}`
params.filter += params.filter ? ` && ${labelFilter}` : labelFilter
}
let projectId = null
const filterId = authStore.settings.frontendSettings.filterIdUsedOnOverview
if (showAll.value && filterId && typeof projectStore.projects[filterId] !== 'undefined') {
@ -246,4 +299,25 @@ watchEffect(() => setTitle(pageTitle.value))
margin: 3rem auto 0;
display: block;
}
.label-filter-info {
margin-block-end: 1rem;
.clear-filter-button {
margin-inline-start: auto;
padding: 0.25rem 0.5rem;
&:hover {
color: var(--danger);
}
}
:deep(.message.info) {
inline-size: 100%;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
}
</style>