Files
policy-ui/app/components/back-office/KanbanColumn.vue
2026-04-29 16:25:11 -05:00

154 lines
3.5 KiB
Vue

<script setup lang="ts">
import KanbanTaskCard from './KanbanTaskCard.vue'
const props = defineProps<{
title: string
status: string
tasks: any[]
loading: boolean
}>()
const router = useRouter()
function navigateToTask(task: any) {
router.push(`/back-office/workload/${encodeURIComponent(task.id)}`)
}
// Stage configuration for colors
const stageConfig: Record<string, {
color: string;
dot: string;
headerBg: string;
}> = {
created: {
color: 'text-[var(--text-muted)]',
dot: 'bg-[var(--text-muted)]',
headerBg: 'bg-[var(--surface)] border-[var(--card-border)]'
},
draft: {
color: 'text-[var(--brand)]',
dot: 'bg-[var(--brand)]',
headerBg: 'bg-[var(--brand-faint)] border-[var(--brand-soft)]'
},
approved: {
color: 'text-emerald-700',
dot: 'bg-emerald-500',
headerBg: 'bg-emerald-50 border-emerald-200'
},
completed: {
color: 'text-gray-500',
dot: 'bg-gray-400',
headerBg: 'bg-gray-50 border-gray-200'
}
}
function daysInStage(createdAt: string): string {
const days = Math.floor((Date.now() - new Date(createdAt).getTime()) / (1000 * 60 * 60 * 24))
return days === 0 ? 'Today' : `${days}d`
}
function taskUrgent(task: any): boolean {
// Placeholder - will be based on priority/due_date from backend
return false
}
</script>
<template>
<div class="kanban-column">
<div class="column-header" :class="stageConfig[status].headerBg">
<div class="flex items-center gap-2">
<span class="h-2 w-2 rounded-full" :class="stageConfig[status].dot" />
<span class="text-[13px] font-semibold" :class="stageConfig[status].color">
{{ title }}
</span>
</div>
<div class="flex items-center gap-1.5">
<span class="flex h-5 min-w-[20px] items-center justify-center rounded-full bg-[var(--surface)]/70 px-1.5 text-[11px] font-semibold text-[var(--text-muted)] ring-1 ring-[var(--card-border)]/60">
{{ tasks.length }}
</span>
</div>
</div>
<div class="task-list">
<KanbanTaskCard
v-for="task in tasks"
:key="task.id"
:task="task"
:urgent="taskUrgent(task)"
@click="navigateToTask(task)"
/>
<div v-if="tasks.length === 0 && !loading" class="empty-state">
<UIcon name="i-heroicons-inbox" class="w-8 h-8" />
</div>
<div v-if="loading" class="loading-state">
<div v-for="n in 3" :key="n" class="skeleton-card" />
</div>
</div>
</div>
</template>
<style scoped>
.kanban-column {
flex: 0 0 240px;
display: flex;
flex-direction: column;
background: var(--surface);
border-radius: 8px;
border: 1px solid var(--card-border);
overflow: hidden;
height: 100%;
}
.column-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
border-bottom: 1px solid var(--card-border);
background: var(--surface);
border-radius: 8px 8px 0 0;
flex-shrink: 0;
}
.task-list {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 8px;
display: flex;
flex-direction: column;
gap: 6px;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 32px 16px;
color: var(--text-muted);
min-height: 100%;
}
.loading-state {
display: flex;
flex-direction: column;
gap: 6px;
padding: 8px;
}
.skeleton-card {
height: 100px;
background: var(--card-border);
border-radius: 6px;
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }
}
</style>