@@ -3,14 +3,15 @@ const draggable = defineAsyncComponent(() => import('vuedraggable'))
33
44interface ColumnItem {
55 id: string
6- name: string
6+ name? : string
77 color? : string | null
88 position? : number
9+ field? : string
910}
1011
1112const props = defineProps <{
13+ mode: ' board' | ' list'
1214 columns: ColumnItem []
13- boardId: string
1415 availableColumns? : ColumnItem []
1516 canAddColumns? : boolean
1617 tags? : Array <{ id: string , name: string , color: string }>
@@ -22,7 +23,7 @@ const props = defineProps<{
2223const open = defineModel <boolean >(' open' , { default: false })
2324
2425const emit = defineEmits <{
25- ' add' : [name : string , color ? : string ]
26+ ' add' : [nameOrField : string , color ? : string ]
2627 ' update' : [columnId : string , updates : { name? : string , color? : string }]
2728 ' delete' : [columnId : string ]
2829 ' reorder' : [columns : { id: string , position: number }[]]
@@ -32,7 +33,35 @@ const emit = defineEmits<{
3233 ' delete-view' : []
3334}>()
3435
35- // Local state — buffered until Save
36+ // ─── List-mode field metadata ───
37+ const ALL_FIELDS = [
38+ { field: ' done' , label: ' Done' , icon: ' i-lucide-circle-check-big' },
39+ { field: ' ticketId' , label: ' Ticket ID' , icon: ' i-lucide-hash' },
40+ { field: ' title' , label: ' Title' , icon: ' i-lucide-type' },
41+ { field: ' status' , label: ' Status' , icon: ' i-lucide-circle-dot' },
42+ { field: ' assignee' , label: ' Assignee' , icon: ' i-lucide-user' },
43+ { field: ' priority' , label: ' Priority' , icon: ' i-lucide-signal' },
44+ { field: ' tags' , label: ' Tags' , icon: ' i-lucide-tag' },
45+ { field: ' dueDate' , label: ' Due Date' , icon: ' i-lucide-calendar' },
46+ { field: ' createdAt' , label: ' Created' , icon: ' i-lucide-calendar-plus' },
47+ { field: ' updatedAt' , label: ' Updated' , icon: ' i-lucide-calendar-clock' },
48+ { field: ' description' , label: ' Description' , icon: ' i-lucide-text' }
49+ ]
50+
51+ const activeFields = computed (() => new Set (localColumns .value .map (c => c .field )))
52+ const availableFields = computed (() =>
53+ ALL_FIELDS .filter (f => ! activeFields .value .has (f .field ))
54+ )
55+
56+ function fieldLabel(field : string ) {
57+ return ALL_FIELDS .find (f => f .field === field )?.label || field
58+ }
59+
60+ function fieldIcon(field : string ) {
61+ return ALL_FIELDS .find (f => f .field === field )?.icon || ' i-lucide-columns-3'
62+ }
63+
64+ // ─── Local state — buffered until Save ───
3665const localColumns = ref <ColumnItem []>([])
3766const localTagFilters = ref <Set <string >>(new Set ())
3867const editName = ref (' ' )
@@ -73,11 +102,12 @@ function onDragEnd() {
73102 // Just reorder locally — emitted on save
74103}
75104
105+ // ─── Board-mode: new column ───
76106const newColumnName = ref (' ' )
77107const newColumnColor = ref (' #6366f1' )
78108const newColorOpen = ref (false )
79109
80- function addColumn () {
110+ function addBoardColumn () {
81111 if (! newColumnName .value .trim ()) return
82112 emit (' add' , newColumnName .value .trim (), newColumnColor .value )
83113 newColumnName .value = ' '
@@ -92,6 +122,7 @@ function pickColor(colId: string, color: string) {
92122 emit (' update' , colId , { color })
93123}
94124
125+ // ─── Tag filter toggle ───
95126function toggleTagFilter(tagId : string ) {
96127 const next = new Set (localTagFilters .value )
97128 if (next .has (tagId )) {
@@ -102,7 +133,7 @@ function toggleTagFilter(tagId: string) {
102133 localTagFilters .value = next
103134}
104135
105- // Dirty detection
136+ // ─── Dirty detection ───
106137const isDirty = computed (() => {
107138 if (editName .value .trim () !== snapshotName .value ) return true
108139 const currentOrder = localColumns .value .map (c => c .id )
@@ -115,7 +146,7 @@ const isDirty = computed(() => {
115146 return false
116147})
117148
118- // Save — emit only what changed
149+ // ─── Save — emit only what changed ───
119150function save() {
120151 if (! isDirty .value ) {
121152 open .value = false
@@ -160,7 +191,7 @@ function discardAndClose() {
160191 open .value = false
161192}
162193
163- // Delete — CardModal-style: button → inline confirmation
194+ // ─── Delete view — inline confirmation ───
164195const showDeleteConfirm = ref (false )
165196const deleteConfirmName = ref (' ' )
166197const deletingView = ref (false )
@@ -179,7 +210,7 @@ function handleDeleteView() {
179210<template >
180211 <UModal
181212 v-model:open =" open"
182- :title =" viewName !== undefined ? undefined : ' Board Settings'"
213+ :title =" viewName !== undefined ? undefined : (mode === 'board' ? ' Board Settings' : 'List Settings') "
183214 :ui =" viewName !== undefined ? { header: 'hidden', body: 'pt-0 sm:pt-0', footer: 'p-0 sm:p-0' } : { footer: 'p-0 sm:p-0' }"
184215 >
185216 <template #body >
@@ -190,7 +221,7 @@ function handleDeleteView() {
190221 <input
191222 v-model =" editName"
192223 type =" text"
193- placeholder =" Board name..."
224+ : placeholder =" mode === 'board' ? ' Board name...' : 'List name...' "
194225 class =" w-full text-[16px] font-semibold text-zinc-900 dark:text-zinc-100 placeholder-zinc-300 dark:placeholder-zinc-600 bg-transparent border-0 border-b border-transparent focus:border-zinc-200 dark:focus:border-zinc-700 rounded-none outline-none! ring-0! tracking-[-0.01em] leading-snug py-2 transition-colors"
195226 @keydown.enter =" ($event.target as HTMLInputElement).blur()"
196227 >
@@ -202,7 +233,9 @@ function handleDeleteView() {
202233 name =" i-lucide-columns-3"
203234 class =" text-[13px] text-zinc-400 dark:text-zinc-500"
204235 />
205- <span class =" text-[12px] font-bold text-zinc-500 dark:text-zinc-400 uppercase tracking-[0.08em]" >Columns</span >
236+ <span class =" text-[12px] font-bold text-zinc-500 dark:text-zinc-400 uppercase tracking-[0.08em]" >
237+ {{ mode === 'board' ? 'Columns' : 'Active Columns' }}
238+ </span >
206239 </div >
207240 <ClientOnly >
208241 <draggable
@@ -222,30 +255,41 @@ function handleDeleteView() {
222255 name =" i-lucide-grip-vertical"
223256 class =" drag-handle text-zinc-300 dark:text-zinc-600 hover:text-zinc-500 dark:hover:text-zinc-400 cursor-grab active:cursor-grabbing text-[15px] shrink-0 transition-colors"
224257 />
225- <UPopover
226- v-if =" canAddColumns"
227- v-model:open =" colorPopoverOpen[col.id]"
228- >
229- <button
230- type =" button"
231- class =" w-3.5 h-3.5 rounded-full shrink-0 ring-1 ring-black/10 dark:ring-white/10 hover:ring-2 hover:ring-indigo-400 transition-all cursor-pointer"
258+ <!-- Board mode: color dot (editable if canAddColumns) -->
259+ <template v-if =" mode === ' board' " >
260+ <UPopover
261+ v-if =" canAddColumns"
262+ v-model:open =" colorPopoverOpen[col.id]"
263+ >
264+ <button
265+ type =" button"
266+ class =" w-3.5 h-3.5 rounded-full shrink-0 ring-1 ring-black/10 dark:ring-white/10 hover:ring-2 hover:ring-indigo-400 transition-all cursor-pointer"
267+ :style =" { backgroundColor: col.color || '#a1a1aa' }"
268+ />
269+ <template #content >
270+ <div class =" p-2" >
271+ <ColorPicker
272+ :model-value =" col.color || '#a1a1aa'"
273+ @update:model-value =" pickColor(col.id, $event)"
274+ />
275+ </div >
276+ </template >
277+ </UPopover >
278+ <div
279+ v-else
280+ class =" w-3.5 h-3.5 rounded-full shrink-0 ring-1 ring-black/10 dark:ring-white/10"
232281 :style =" { backgroundColor: col.color || '#a1a1aa' }"
233282 />
234- <template #content >
235- <div class =" p-2" >
236- <ColorPicker
237- :model-value =" col.color || '#a1a1aa'"
238- @update:model-value =" pickColor(col.id, $event)"
239- />
240- </div >
241- </template >
242- </UPopover >
243- <div
244- v-else
245- class =" w-3.5 h-3.5 rounded-full shrink-0 ring-1 ring-black/10 dark:ring-white/10"
246- :style =" { backgroundColor: col.color || '#a1a1aa' }"
283+ </template >
284+ <!-- List mode: field icon -->
285+ <UIcon
286+ v-if =" mode === 'list'"
287+ :name =" fieldIcon(col.field || '')"
288+ class =" text-[14px] text-zinc-400 dark:text-zinc-500 shrink-0"
247289 />
248- <span class =" text-[14px] font-medium flex-1" >{{ col.name }}</span >
290+ <span class =" text-[14px] font-medium flex-1" >
291+ {{ mode === 'board' ? col.name : fieldLabel(col.field || '') }}
292+ </span >
249293 <div class =" flex items-center gap-0.5 opacity-0 sm:group-hover:opacity-100 max-sm:opacity-60 transition-opacity" >
250294 <UTooltip text =" Remove column" >
251295 <UButton
@@ -264,10 +308,11 @@ function handleDeleteView() {
264308
265309 <USeparator class =" my-2" />
266310
311+ <!-- Board mode: add new column -->
267312 <form
268- v-if =" canAddColumns"
313+ v-if =" mode === 'board' && canAddColumns"
269314 class =" flex items-center gap-2"
270- @submit.prevent =" addColumn "
315+ @submit.prevent =" addBoardColumn "
271316 >
272317 <UPopover v-model:open =" newColorOpen" >
273318 <button
@@ -295,7 +340,8 @@ function handleDeleteView() {
295340 />
296341 </form >
297342
298- <template v-if =" availableColumns ?.length " >
343+ <!-- Board mode: available columns to link -->
344+ <template v-if =" mode === ' board' && availableColumns ?.length " >
299345 <USeparator class =" my-2" />
300346 <div class =" flex items-center gap-1.5 mb-1" >
301347 <UIcon
@@ -324,6 +370,36 @@ function handleDeleteView() {
324370 </div >
325371 </template >
326372
373+ <!-- List mode: available fields -->
374+ <template v-if =" mode === ' list' && availableFields .length " >
375+ <div class =" flex items-center gap-1.5 mb-1" >
376+ <UIcon
377+ name =" i-lucide-plus-circle"
378+ class =" text-[13px] text-zinc-400 dark:text-zinc-500"
379+ />
380+ <span class =" text-[12px] font-bold text-zinc-500 dark:text-zinc-400 uppercase tracking-[0.08em]" >Available Fields</span >
381+ </div >
382+ <div
383+ v-for =" f in availableFields"
384+ :key =" f.field"
385+ class =" flex items-center gap-2 px-2 py-2 rounded-lg hover:bg-zinc-50 dark:hover:bg-zinc-800/50 transition-colors group"
386+ >
387+ <UIcon
388+ :name =" f.icon"
389+ class =" text-[14px] text-zinc-300 dark:text-zinc-600 shrink-0"
390+ />
391+ <span class =" text-[14px] font-medium flex-1 text-zinc-400 dark:text-zinc-500" >{{ f.label }}</span >
392+ <UButton
393+ icon =" i-lucide-plus"
394+ variant =" ghost"
395+ color =" neutral"
396+ size =" xs"
397+ @click =" emit('add', f.field)"
398+ />
399+ </div >
400+ </template >
401+
402+ <!-- Tag filters (shared) -->
327403 <template v-if =" tags ?.length " >
328404 <USeparator class =" my-2" />
329405 <div class =" flex items-center gap-1.5 mb-1" >
0 commit comments