154 lines
3.5 KiB
Vue
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>
|