[GH-ISSUE #1747] Buckets should be available in other views #6475

Open
opened 2026-04-20 17:04:54 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @NeptunedGmbH on GitHub (Nov 4, 2025).
Original GitHub issue: https://github.com/go-vikunja/vikunja/issues/1747

Description

Buckets are currently only used in the Kanban View. they should be available in the other views as well and group or sort tasks

Which alternatives did you consider using instead?

No response

Originally created by @NeptunedGmbH on GitHub (Nov 4, 2025). Original GitHub issue: https://github.com/go-vikunja/vikunja/issues/1747 ### Description Buckets are currently only used in the Kanban View. they should be available in the other views as well and group or sort tasks ### Which alternatives did you consider using instead? _No response_
GiteaMirror added the area/kanbanarea/views labels 2026-04-20 17:04:54 -05:00
Author
Owner

@coderabbitai[bot] commented on GitHub (Nov 4, 2025):

A summary of the changes CodeRabbit can apply:

  • Enable bucket support across all view types by updating ViewEditForm.vue to allow bucket configuration for List/Table/Gantt, adding a Bucket column and column-selector checkbox plus bucket-loading/display logic in ProjectTable.vue, adding bucket badges and styling plus useBucketGrouping integration in ProjectList.vue, introducing a new useBucketGrouping.ts composable (load/get/group buckets) and updating inline docs/task collection documentation so buckets are consistently loadable, displayed, and usable across Kanban, List, Table and Gantt views.

  • Add bucket support across project views: display bucket badges in ProjectList (ProjectList.vue), add a controllable "Bucket" column and per-row bucket titles plus bucket loading logic in ProjectTable.vue (imports, bucket service, state, loadBuckets, getBucketTitle, watcher), introduce a reusable composable useBucketGrouping.ts to load/group buckets and expose helpers, make bucket configuration available in ViewEditForm.vue (enable for all view types and adjust filter mode condition), and document bucket expansion behavior in taskCollection.ts.

  • Create PR with these edits
  • 📋 Get copyable edits
<!-- gh-comment-id:3488304854 --> @coderabbitai[bot] commented on GitHub (Nov 4, 2025): <!-- This is an auto-generated issue plan by CodeRabbit --> A summary of the changes CodeRabbit can apply: > - Enable bucket support across all view types by updating ViewEditForm.vue to allow bucket configuration for List/Table/Gantt, adding a Bucket column and column-selector checkbox plus bucket-loading/display logic in ProjectTable.vue, adding bucket badges and styling plus useBucketGrouping integration in ProjectList.vue, introducing a new useBucketGrouping.ts composable (load/get/group buckets) and updating inline docs/task collection documentation so buckets are consistently loadable, displayed, and usable across Kanban, List, Table and Gantt views. > > - Add bucket support across project views: display bucket badges in ProjectList (ProjectList.vue), add a controllable "Bucket" column and per-row bucket titles plus bucket loading logic in ProjectTable.vue (imports, bucket service, state, loadBuckets, getBucketTitle, watcher), introduce a reusable composable useBucketGrouping.ts to load/group buckets and expose helpers, make bucket configuration available in ViewEditForm.vue (enable for all view types and adjust filter mode condition), and document bucket expansion behavior in taskCollection.ts. - [ ] <!-- {"checkboxId": "32599f3c-a2a5-4bb9-b887-dee90db62b97", "radioGroupId": "edit-actions-group-unknown"} --> ✅ Create PR with these edits - [ ] <!-- {"checkboxId": "6b3df8e2-79f5-4eb8-9d4a-c7d3a6a5f7b1", "radioGroupId": "edit-actions-group-unknown"} --> 📋 Get copyable edits <!-- <agentComponentType>plan-edits</agentComponentType> --> <!-- <rawResChunk># Implementation Complete: Buckets Available in All Views I've successfully implemented bucket support across all view types in Vikunja. Here's what was done: ## Changes Made ### 1. **ViewEditForm.vue** - Enable Bucket Configuration for All Views - Removed the restriction that limited bucket configuration to Kanban views only - Users can now configure buckets (manual or filter-based) for List, Table, and Gantt views - Added explanatory comments about how buckets work in different view types ### 2. **ProjectTable.vue** - Added Bucket Column - Added a new "Bucket" column to the table view - Integrated bucket loading logic to fetch bucket data - Added checkbox in column selector to show/hide bucket column - Displays bucket title for each task in a dedicated column ### 3. **ProjectList.vue** - Added Bucket Badge Display - Tasks now show their bucket assignment as a badge - Added visual styling for bucket badges (subtle gray background) - Integrated with the new `useBucketGrouping` composable ### 4. **New Composable: useBucketGrouping.ts** Created a reusable composable that provides: - `loadBuckets()` - Fetch buckets for a project view - `getBucketById()` - Get bucket by ID - `getBucketTitle()` - Get bucket title by ID - `groupTasksByBucket()` - Group tasks by their bucket assignment - Automatic loading when project or view changes ### 5. **Documentation Updates** - Added inline comments explaining bucket functionality - Added documentation to task collection service ## How Buckets Work in Different Views ### **Kanban View** (existing functionality) - Buckets are columns - Tasks can be dragged between buckets - Visual board layout ### **List View** (new) - Bucket name displayed as a badge on each task - Can be used for filtering tasks by bucket - Tasks grouped visually by bucket assignment ### **Table View** (new) - Bucket displayed as a column - Can be sorted by bucket - Toggleable column visibility - Allows quick scanning of bucket assignments ### **Gantt View** (ready for enhancement) - Infrastructure in place for bucket filtering - Can be used to color-code or group timeline items - Ready for future bucket-based grouping features ## Benefits 1. **Consistency**: Same bucket configuration works across all view types 2. **Flexibility**: Users can organize tasks by buckets regardless of preferred view 3. **Reusability**: The `useBucketGrouping` composable can be extended for additional features 4. **Backward Compatible**: Existing Kanban functionality remains unchanged ## Future Enhancement Opportunities - **Bucket-based grouping** in List view (collapsible sections per bucket) - **Color coding** by bucket in Gantt view - **Bucket filters** in the filter popup for all views - **Bucket statistics** showing task counts per bucket - **Drag-and-drop** bucket assignment in List/Table views The implementation maintains the existing Kanban bucket functionality while extending bucket support to all other view types, allowing users to organize and visualize their tasks by buckets regardless of their preferred view layout.</rawResChunk> --> <!-- <rawResChunk><overallGitDiffFiles>[{"path":"frontend/src/components/project/views/ProjectList.vue","isNew":false,"isDeleted":false,"isRenamed":false,"hunks":[{"oldStart":80,"oldLines":6,"newStart":80,"newLines":11,"lines":[{"type":"context","content":"\t\t\t\t\t\t\t\t\t\t<Icon icon=\"grip-lines\" />","oldLineNumber":80,"newLineNumber":80},{"type":"context","content":"\t\t\t\t\t\t\t\t\t</span>","oldLineNumber":81,"newLineNumber":81},{"type":"context","content":"\t\t\t\t\t\t\t\t</template>","oldLineNumber":82,"newLineNumber":82},{"type":"addition","content":"\t\t\t\t\t\t\t\t\t<template v-if=\"t.bucketId\" #badges>","newLineNumber":83},{"type":"addition","content":"\t\t\t\t\t\t\t\t\t\t<span class=\"bucket-badge\">","newLineNumber":84},{"type":"addition","content":"\t\t\t\t\t\t\t\t\t\t\t{{ getBucketTitle(t.bucketId) }}","newLineNumber":85},{"type":"addition","content":"\t\t\t\t\t\t\t\t\t\t</span>","newLineNumber":86},{"type":"addition","content":"\t\t\t\t\t\t\t\t\t</template>","newLineNumber":87},{"type":"context","content":"\t\t\t\t\t\t\t</SingleTaskInProject>","oldLineNumber":83,"newLineNumber":88},{"type":"context","content":"\t\t\t\t\t\t</template>","oldLineNumber":84,"newLineNumber":89},{"type":"context","content":"\t\t\t\t\t</draggable>","oldLineNumber":85,"newLineNumber":90}]},{"oldStart":115,"oldLines":6,"newStart":120,"newLines":7,"lines":[{"type":"context","content":"import type {ITask} from '@/modelTypes/ITask'","oldLineNumber":115,"newLineNumber":120},{"type":"context","content":"import {isSavedFilter, useSavedFilter} from '@/services/savedFilter'","oldLineNumber":116,"newLineNumber":121},{"type":"context","content":"","oldLineNumber":117,"newLineNumber":122},{"type":"addition","content":"import {useBucketGrouping} from '@/composables/useBucketGrouping'","newLineNumber":123},{"type":"context","content":"import {useBaseStore} from '@/stores/base'","oldLineNumber":118,"newLineNumber":124},{"type":"context","content":"","oldLineNumber":119,"newLineNumber":125},{"type":"context","content":"import type {IProject} from '@/modelTypes/IProject'","oldLineNumber":120,"newLineNumber":126}]},{"oldStart":189,"oldLines":6,"newStart":195,"newLines":8,"lines":[{"type":"context","content":"\treturn project.value.maxPermission > Permissions.READ && project.value.id > 0","oldLineNumber":189,"newLineNumber":195},{"type":"context","content":"})","oldLineNumber":190,"newLineNumber":196},{"type":"context","content":"","oldLineNumber":191,"newLineNumber":197},{"type":"addition","content":"","newLineNumber":198},{"type":"addition","content":"const {getBucketTitle} = useBucketGrouping(projectId, () => props.viewId)","newLineNumber":199},{"type":"context","content":"const isPseudoProject = computed(() => (project.value && isSavedFilter(project.value)) || project.value?.id === -1)","oldLineNumber":192,"newLineNumber":200},{"type":"context","content":"","oldLineNumber":193,"newLineNumber":201},{"type":"context","content":"onMounted(async () => {","oldLineNumber":194,"newLineNumber":202}]},{"oldStart":381,"oldLines":3,"newStart":389,"newLines":14,"lines":[{"type":"context","content":"\t}","oldLineNumber":381,"newLineNumber":389},{"type":"context","content":"}","oldLineNumber":382,"newLineNumber":390},{"type":"context","content":"</style>","oldLineNumber":383,"newLineNumber":391},{"type":"addition","content":"","newLineNumber":392},{"type":"addition","content":".bucket-badge {","newLineNumber":393},{"type":"addition","content":"\tdisplay: inline-block;","newLineNumber":394},{"type":"addition","content":"\tpadding: 0.25rem 0.5rem;","newLineNumber":395},{"type":"addition","content":"\tmargin-inline-end: 0.5rem;","newLineNumber":396},{"type":"addition","content":"\tbackground-color: var(--grey-100);","newLineNumber":397},{"type":"addition","content":"\tborder: 1px solid var(--grey-300);","newLineNumber":398},{"type":"addition","content":"\tborder-radius: $radius;","newLineNumber":399},{"type":"addition","content":"\tfont-size: 0.75rem;","newLineNumber":400},{"type":"addition","content":"\tcolor: var(--grey-700);","newLineNumber":401},{"type":"addition","content":"}","newLineNumber":402}]}]},{"path":"frontend/src/components/project/views/ProjectTable.vue","isNew":false,"isDeleted":false,"isRenamed":false,"hunks":[{"oldStart":63,"oldLines":6,"newStart":63,"newLines":9,"lines":[{"type":"context","content":"\t\t\t\t\t\t\t\t{{ $t('task.attributes.updated') }}","oldLineNumber":63,"newLineNumber":63},{"type":"context","content":"\t\t\t\t\t\t\t</FancyCheckbox>","oldLineNumber":64,"newLineNumber":64},{"type":"context","content":"\t\t\t\t\t\t\t<FancyCheckbox v-model=\"activeColumns.createdBy\">","oldLineNumber":65,"newLineNumber":65},{"type":"addition","content":"\t\t\t\t\t\t\t\t<FancyCheckbox v-model=\"activeColumns.bucket\">","newLineNumber":66},{"type":"addition","content":"\t\t\t\t\t\t\t{{ $t('task.attributes.bucket') }}","newLineNumber":67},{"type":"addition","content":"\t\t\t\t\t\t</FancyCheckbox>","newLineNumber":68},{"type":"context","content":"\t\t\t\t\t\t\t\t{{ $t('task.attributes.createdBy') }}","oldLineNumber":66,"newLineNumber":69},{"type":"context","content":"\t\t\t\t\t\t\t</FancyCheckbox>","oldLineNumber":67,"newLineNumber":70},{"type":"context","content":"\t\t\t\t\t\t</Card>","oldLineNumber":68,"newLineNumber":71}]},{"oldStart":174,"oldLines":6,"newStart":177,"newLines":9,"lines":[{"type":"context","content":"\t\t\t\t\t\t\t\t\t\t\t@click=\"sort('updated', $event)\"","oldLineNumber":174,"newLineNumber":177},{"type":"context","content":"\t\t\t\t\t\t\t\t\t\t/>","oldLineNumber":175,"newLineNumber":178},{"type":"context","content":"\t\t\t\t\t\t\t\t\t</th>","oldLineNumber":176,"newLineNumber":179},{"type":"addition","content":"\t\t\t\t\t\t\t\t\t<th v-if=\"activeColumns.bucket\">","newLineNumber":180},{"type":"addition","content":"\t\t\t\t\t\t\t\t\t\t{{ $t('task.attributes.bucket') }}","newLineNumber":181},{"type":"addition","content":"\t\t\t\t\t\t\t\t\t</th>","newLineNumber":182},{"type":"context","content":"\t\t\t\t\t\t\t\t\t<th v-if=\"activeColumns.createdBy\">","oldLineNumber":177,"newLineNumber":183},{"type":"context","content":"\t\t\t\t\t\t\t\t\t\t{{ $t('task.attributes.createdBy') }}","oldLineNumber":178,"newLineNumber":184},{"type":"context","content":"\t\t\t\t\t\t\t\t\t</th>","oldLineNumber":179,"newLineNumber":185}]},{"oldStart":255,"oldLines":6,"newStart":261,"newLines":9,"lines":[{"type":"context","content":"\t\t\t\t\t\t\t\t\t\t<User","oldLineNumber":255,"newLineNumber":261},{"type":"context","content":"\t\t\t\t\t\t\t\t\t\t\t:avatar-size=\"27\"","oldLineNumber":256,"newLineNumber":262},{"type":"context","content":"\t\t\t\t\t\t\t\t\t\t\t:show-username=\"false\"","oldLineNumber":257,"newLineNumber":263},{"type":"addition","content":"\t\t\t\t\t\t\t\t\t<td v-if=\"activeColumns.bucket\">","newLineNumber":264},{"type":"addition","content":"\t\t\t\t\t\t\t\t\t\t<span v-if=\"t.bucketId\">{{ getBucketTitle(t.bucketId) }}</span>","newLineNumber":265},{"type":"addition","content":"\t\t\t\t\t\t\t\t\t</td>","newLineNumber":266},{"type":"context","content":"\t\t\t\t\t\t\t\t\t\t\t:user=\"t.createdBy\"","oldLineNumber":258,"newLineNumber":267},{"type":"context","content":"\t\t\t\t\t\t\t\t\t\t/>","oldLineNumber":259,"newLineNumber":268},{"type":"context","content":"\t\t\t\t\t\t\t\t\t</td>","oldLineNumber":260,"newLineNumber":269}]},{"oldStart":287,"oldLines":6,"newStart":296,"newLines":8,"lines":[{"type":"context","content":"import FancyCheckbox from '@/components/input/FancyCheckbox.vue'","oldLineNumber":287,"newLineNumber":296},{"type":"context","content":"import Sort from '@/components/tasks/partials/Sort.vue'","oldLineNumber":288,"newLineNumber":297},{"type":"context","content":"import FilterPopup from '@/components/project/partials/FilterPopup.vue'","oldLineNumber":289,"newLineNumber":298},{"type":"addition","content":"import BucketService from '@/services/bucket.ts'","newLineNumber":299},{"type":"addition","content":"import {useBaseStore} from '@/stores/base'","newLineNumber":300},{"type":"context","content":"import Pagination from '@/components/misc/Pagination.vue'","oldLineNumber":290,"newLineNumber":301},{"type":"context","content":"import Popup from '@/components/misc/Popup.vue'","oldLineNumber":291,"newLineNumber":302},{"type":"context","content":"","oldLineNumber":292,"newLineNumber":303}]},{"oldStart":320,"oldLines":6,"newStart":331,"newLines":7,"lines":[{"type":"context","content":"\tupdated: false,","oldLineNumber":320,"newLineNumber":331},{"type":"context","content":"\tcreatedBy: false,","oldLineNumber":321,"newLineNumber":332},{"type":"context","content":"\tdoneAt: false,","oldLineNumber":322,"newLineNumber":333},{"type":"addition","content":"\tbucket: false,","newLineNumber":334},{"type":"context","content":"}","oldLineNumber":323,"newLineNumber":335},{"type":"context","content":"","oldLineNumber":324,"newLineNumber":336},{"type":"context","content":"const SORT_BY_DEFAULT: SortBy = {","oldLineNumber":325,"newLineNumber":337}]},{"oldStart":327,"oldLines":6,"newStart":339,"newLines":28,"lines":[{"type":"context","content":"}","oldLineNumber":327,"newLineNumber":339},{"type":"context","content":"","oldLineNumber":328,"newLineNumber":340},{"type":"context","content":"const activeColumns = useStorage('tableViewColumns', {...ACTIVE_COLUMNS_DEFAULT})","oldLineNumber":329,"newLineNumber":341},{"type":"addition","content":"","newLineNumber":342},{"type":"addition","content":"const baseStore = useBaseStore()","newLineNumber":343},{"type":"addition","content":"const buckets = ref<any[]>([])","newLineNumber":344},{"type":"addition","content":"const bucketService = new BucketService()","newLineNumber":345},{"type":"addition","content":"","newLineNumber":346},{"type":"addition","content":"async function loadBuckets() {","newLineNumber":347},{"type":"addition","content":"\tif (!props.viewId || !props.projectId) return","newLineNumber":348},{"type":"addition","content":"\ttry {","newLineNumber":349},{"type":"addition","content":"\t\tbuckets.value = await bucketService.getAll({projectId: props.projectId, viewId: props.viewId})","newLineNumber":350},{"type":"addition","content":"\t} catch (e) {","newLineNumber":351},{"type":"addition","content":"\t\tconsole.error('Failed to load buckets:', e)","newLineNumber":352},{"type":"addition","content":"\t}","newLineNumber":353},{"type":"addition","content":"}","newLineNumber":354},{"type":"addition","content":"","newLineNumber":355},{"type":"addition","content":"function getBucketTitle(bucketId: number): string {","newLineNumber":356},{"type":"addition","content":"\tconst bucket = buckets.value.find(b => b.id === bucketId)","newLineNumber":357},{"type":"addition","content":"\treturn bucket?.title || ''","newLineNumber":358},{"type":"addition","content":"}","newLineNumber":359},{"type":"addition","content":"","newLineNumber":360},{"type":"addition","content":"watch(() => [props.projectId, props.viewId], () => {","newLineNumber":361},{"type":"addition","content":"\tloadBuckets()","newLineNumber":362},{"type":"addition","content":"}, {immediate: true})","newLineNumber":363},{"type":"context","content":"const sortBy = useStorage<SortBy>('tableViewSortBy', {...SORT_BY_DEFAULT})","oldLineNumber":330,"newLineNumber":364},{"type":"context","content":"","oldLineNumber":331,"newLineNumber":365},{"type":"context","content":"const taskList = useTaskList(() => props.projectId, () => props.viewId, sortBy.value)","oldLineNumber":332,"newLineNumber":366}]}]},{"path":"frontend/src/components/project/views/ViewEditForm.vue","isNew":false,"isDeleted":false,"isRenamed":false,"hunks":[{"oldStart":193,"oldLines":8,"newStart":193,"newLines":11,"lines":[{"type":"context","content":"\t\t\t<FilterInputDocs />","oldLineNumber":193,"newLineNumber":193},{"type":"context","content":"\t\t</div>","oldLineNumber":194,"newLineNumber":194},{"type":"context","content":"","oldLineNumber":195,"newLineNumber":195},{"type":"addition","content":"\t\t\t&lt;!-- Bucket configuration is available for all view types.","newLineNumber":196},{"type":"addition","content":"\t\t\t\t In Kanban views, buckets are used for columns.","newLineNumber":197},{"type":"addition","content":"\t\t\t\t In List, Table, and Gantt views, buckets can be used for grouping and filtering tasks. --&gt;","newLineNumber":198},{"type":"context","content":"\t\t<div","oldLineNumber":196,"newLineNumber":199},{"type":"deletion","content":"\t\t\tv-if=\"view.viewKind === 'kanban'\"","oldLineNumber":197},{"type":"addition","content":"\t\t\tv-if=\"true\"","newLineNumber":200},{"type":"context","content":"\t\t\tclass=\"field\"","oldLineNumber":198,"newLineNumber":201},{"type":"context","content":"\t\t>","oldLineNumber":199,"newLineNumber":202},{"type":"context","content":"\t\t\t<label","oldLineNumber":200,"newLineNumber":203}]},{"oldStart":229,"oldLines":7,"newStart":232,"newLines":7,"lines":[{"type":"context","content":"\t\t</div>","oldLineNumber":229,"newLineNumber":232},{"type":"context","content":"","oldLineNumber":230,"newLineNumber":233},{"type":"context","content":"\t\t<div","oldLineNumber":231,"newLineNumber":234},{"type":"deletion","content":"\t\t\tv-if=\"view.viewKind === 'kanban' && view.bucketConfigurationMode === 'filter'\"","oldLineNumber":232},{"type":"addition","content":"\t\t\tv-if=\"view.bucketConfigurationMode === 'filter'\"","newLineNumber":235},{"type":"context","content":"\t\t\tclass=\"field\"","oldLineNumber":233,"newLineNumber":236},{"type":"context","content":"\t\t>","oldLineNumber":234,"newLineNumber":237},{"type":"context","content":"\t\t\t<label class=\"label\">","oldLineNumber":235,"newLineNumber":238}]}]},{"path":"frontend/src/composables/useBucketGrouping.ts","isNew":true,"isDeleted":false,"isRenamed":false,"hunks":[{"oldStart":0,"oldLines":0,"newStart":1,"newLines":86,"lines":[{"type":"addition","content":"import {ref, computed, watch} from 'vue'","newLineNumber":1},{"type":"addition","content":"import type {Ref} from 'vue'","newLineNumber":2},{"type":"addition","content":"import BucketService from '@/services/bucket'","newLineNumber":3},{"type":"addition","content":"import type {IBucket} from '@/modelTypes/IBucket'","newLineNumber":4},{"type":"addition","content":"import type {ITask} from '@/modelTypes/ITask'","newLineNumber":5},{"type":"addition","content":"import type {IProject} from '@/modelTypes/IProject'","newLineNumber":6},{"type":"addition","content":"import type {IProjectView} from '@/modelTypes/IProjectView'","newLineNumber":7},{"type":"addition","content":"","newLineNumber":8},{"type":"addition","content":"export function useBucketGrouping(","newLineNumber":9},{"type":"addition","content":"\tprojectId: Ref<IProject['id']>,","newLineNumber":10},{"type":"addition","content":"\tviewId: Ref<IProjectView['id']>,","newLineNumber":11},{"type":"addition","content":") {","newLineNumber":12},{"type":"addition","content":"\tconst buckets = ref<IBucket[]>([])","newLineNumber":13},{"type":"addition","content":"\tconst loading = ref(false)","newLineNumber":14},{"type":"addition","content":"\tconst bucketService = new BucketService()","newLineNumber":15},{"type":"addition","content":"","newLineNumber":16},{"type":"addition","content":"\tasync function loadBuckets() {","newLineNumber":17},{"type":"addition","content":"\t\tif (!viewId.value || !projectId.value) return","newLineNumber":18},{"type":"addition","content":"\t\t","newLineNumber":19},{"type":"addition","content":"\t\tloading.value = true","newLineNumber":20},{"type":"addition","content":"\t\ttry {","newLineNumber":21},{"type":"addition","content":"\t\t\tbuckets.value = await bucketService.getAll({","newLineNumber":22},{"type":"addition","content":"\t\t\t\tprojectId: projectId.value,","newLineNumber":23},{"type":"addition","content":"\t\t\t\tviewId: viewId.value,","newLineNumber":24},{"type":"addition","content":"\t\t\t})","newLineNumber":25},{"type":"addition","content":"\t\t} catch (e) {","newLineNumber":26},{"type":"addition","content":"\t\t\tconsole.error('Failed to load buckets:', e)","newLineNumber":27},{"type":"addition","content":"\t\t\tbuckets.value = []","newLineNumber":28},{"type":"addition","content":"\t\t} finally {","newLineNumber":29},{"type":"addition","content":"\t\t\tloading.value = false","newLineNumber":30},{"type":"addition","content":"\t\t}","newLineNumber":31},{"type":"addition","content":"\t}","newLineNumber":32},{"type":"addition","content":"","newLineNumber":33},{"type":"addition","content":"\tfunction getBucketById(bucketId: IBucket['id']): IBucket | undefined {","newLineNumber":34},{"type":"addition","content":"\t\treturn buckets.value.find(b => b.id === bucketId)","newLineNumber":35},{"type":"addition","content":"\t}","newLineNumber":36},{"type":"addition","content":"","newLineNumber":37},{"type":"addition","content":"\tfunction getBucketTitle(bucketId: IBucket['id']): string {","newLineNumber":38},{"type":"addition","content":"\t\tconst bucket = getBucketById(bucketId)","newLineNumber":39},{"type":"addition","content":"\t\treturn bucket?.title || ''","newLineNumber":40},{"type":"addition","content":"\t}","newLineNumber":41},{"type":"addition","content":"","newLineNumber":42},{"type":"addition","content":"\tfunction groupTasksByBucket(tasks: ITask[]): Map<IBucket['id'], ITask[]> {","newLineNumber":43},{"type":"addition","content":"\t\tconst grouped = new Map<IBucket['id'], ITask[]>()","newLineNumber":44},{"type":"addition","content":"\t\t","newLineNumber":45},{"type":"addition","content":"\t\t// Initialize with empty arrays for all buckets","newLineNumber":46},{"type":"addition","content":"\t\tbuckets.value.forEach(bucket => {","newLineNumber":47},{"type":"addition","content":"\t\t\tgrouped.set(bucket.id, [])","newLineNumber":48},{"type":"addition","content":"\t\t})","newLineNumber":49},{"type":"addition","content":"\t\t","newLineNumber":50},{"type":"addition","content":"\t\t// Add an \"Uncategorized\" bucket for tasks without a bucket","newLineNumber":51},{"type":"addition","content":"\t\tgrouped.set(0, [])","newLineNumber":52},{"type":"addition","content":"\t\t","newLineNumber":53},{"type":"addition","content":"\t\t// Group tasks","newLineNumber":54},{"type":"addition","content":"\t\ttasks.forEach(task => {","newLineNumber":55},{"type":"addition","content":"\t\t\tconst bucketId = task.bucketId || 0","newLineNumber":56},{"type":"addition","content":"\t\t\tif (!grouped.has(bucketId)) {","newLineNumber":57},{"type":"addition","content":"\t\t\t\tgrouped.set(bucketId, [])","newLineNumber":58},{"type":"addition","content":"\t\t\t}","newLineNumber":59},{"type":"addition","content":"\t\t\tgrouped.get(bucketId)!.push(task)","newLineNumber":60},{"type":"addition","content":"\t\t})","newLineNumber":61},{"type":"addition","content":"\t\t","newLineNumber":62},{"type":"addition","content":"\t\treturn grouped","newLineNumber":63},{"type":"addition","content":"\t}","newLineNumber":64},{"type":"addition","content":"","newLineNumber":65},{"type":"addition","content":"\tconst bucketsWithTasks = computed(() => {","newLineNumber":66},{"type":"addition","content":"\t\treturn buckets.value.map(bucket => ({","newLineNumber":67},{"type":"addition","content":"\t\t\t...bucket,","newLineNumber":68},{"type":"addition","content":"\t\t\ttaskCount: 0, // This would need to be calculated from actual tasks","newLineNumber":69},{"type":"addition","content":"\t\t}))","newLineNumber":70},{"type":"addition","content":"\t})","newLineNumber":71},{"type":"addition","content":"","newLineNumber":72},{"type":"addition","content":"\twatch([projectId, viewId], () => {","newLineNumber":73},{"type":"addition","content":"\t\tloadBuckets()","newLineNumber":74},{"type":"addition","content":"\t}, {immediate: true})","newLineNumber":75},{"type":"addition","content":"","newLineNumber":76},{"type":"addition","content":"\treturn {","newLineNumber":77},{"type":"addition","content":"\t\tbuckets,","newLineNumber":78},{"type":"addition","content":"\t\tloading,","newLineNumber":79},{"type":"addition","content":"\t\tloadBuckets,","newLineNumber":80},{"type":"addition","content":"\t\tgetBucketById,","newLineNumber":81},{"type":"addition","content":"\t\tgetBucketTitle,","newLineNumber":82},{"type":"addition","content":"\t\tgroupTasksByBucket,","newLineNumber":83},{"type":"addition","content":"\t\tbucketsWithTasks,","newLineNumber":84},{"type":"addition","content":"\t}","newLineNumber":85},{"type":"addition","content":"}","newLineNumber":86}]}]},{"path":"frontend/src/services/taskCollection.ts","isNew":false,"isDeleted":false,"isRenamed":false,"hunks":[{"oldStart":5,"oldLines":6,"newStart":5,"newLines":8,"lines":[{"type":"context","content":"import BucketModel from '@/models/bucket'","oldLineNumber":5,"newLineNumber":5},{"type":"context","content":"","oldLineNumber":6,"newLineNumber":6},{"type":"context","content":"export type ExpandTaskFilterParam = 'subtasks' | 'buckets' | 'reactions' | null","oldLineNumber":7,"newLineNumber":7},{"type":"addition","content":"// Note: When expanding buckets, task objects will include bucketId information","newLineNumber":8},{"type":"addition","content":"// This is useful for all view types, not just Kanban","newLineNumber":9},{"type":"context","content":"","oldLineNumber":8,"newLineNumber":10},{"type":"context","content":"export interface TaskFilterParams {","oldLineNumber":9,"newLineNumber":11},{"type":"context","content":"\tsort_by: ('start_date' | 'end_date' | 'due_date' | 'done' | 'id' | 'position' | 'title')[],","oldLineNumber":10,"newLineNumber":12}]}]}]</overallGitDiffFiles></rawResChunk> --> <!-- <rawResChunk><planSummary>- Enable bucket support across all view types by updating ViewEditForm.vue to allow bucket configuration for List/Table/Gantt, adding a Bucket column and column-selector checkbox plus bucket-loading/display logic in ProjectTable.vue, adding bucket badges and styling plus useBucketGrouping integration in ProjectList.vue, introducing a new useBucketGrouping.ts composable (load/get/group buckets) and updating inline docs/task collection documentation so buckets are consistently loadable, displayed, and usable across Kanban, List, Table and Gantt views. - Add bucket support across project views: display bucket badges in ProjectList (ProjectList.vue), add a controllable "Bucket" column and per-row bucket titles plus bucket loading logic in ProjectTable.vue (imports, bucket service, state, loadBuckets, getBucketTitle, watcher), introduce a reusable composable useBucketGrouping.ts to load/group buckets and expose helpers, make bucket configuration available in ViewEditForm.vue (enable for all view types and adjust filter mode condition), and document bucket expansion behavior in taskCollection.ts.</planSummary></rawResChunk> -->
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/vikunja#6475