184 lines
5.8 KiB
Vue
184 lines
5.8 KiB
Vue
<script setup lang="ts">
|
|
import type { SelectItem } from '@nuxt/ui'
|
|
|
|
usePageTitle('Workload Tasks')
|
|
|
|
const page = ref(1)
|
|
const statusFilter = ref<string | null>(null)
|
|
const policyTypeFilter = ref<string | null>(null)
|
|
|
|
const statusItems = ref<SelectItem[]>([
|
|
{ label: 'All Statuses', value: null },
|
|
{ label: 'Created', value: 'created' },
|
|
{ label: 'Draft', value: 'draft' },
|
|
{ label: 'Approved', value: 'approved' },
|
|
{ label: 'Completed', value: 'completed' }
|
|
])
|
|
|
|
const policyTypeItems = ref<SelectItem[]>([
|
|
{ label: 'All Types', value: null },
|
|
{ label: 'Car', value: 'car' },
|
|
{ label: 'Life', value: 'life' },
|
|
{ label: 'Fire', value: 'fire' }
|
|
])
|
|
|
|
watch([statusFilter, policyTypeFilter], () => { page.value = 1 })
|
|
|
|
const { data, pending, error, refresh } = useWorkload('/tasks', {
|
|
query: computed(() => ({
|
|
page: page.value,
|
|
page_size: 20,
|
|
...(statusFilter.value && {
|
|
'filters[0][field]': 'status',
|
|
'filters[0][op]': '==',
|
|
'filters[0][value]': statusFilter.value
|
|
}),
|
|
...(policyTypeFilter.value && {
|
|
'filters[1][field]': 'policy_type',
|
|
'filters[1][op]': '==',
|
|
'filters[1][value]': policyTypeFilter.value
|
|
})
|
|
}))
|
|
})
|
|
|
|
const tasks = computed(() => data.value?.data ?? [])
|
|
const meta = computed(() => data.value?.meta)
|
|
const total = computed(() => meta.value?.total_count ?? 0)
|
|
const totalPages = computed(() => meta.value?.total_pages ?? 0)
|
|
|
|
const statusColor = (status: string) => {
|
|
switch (status) {
|
|
case 'created': return 'yellow'
|
|
case 'draft': return 'blue'
|
|
case 'approved': return 'green'
|
|
case 'completed': return 'gray'
|
|
default: return 'gray'
|
|
}
|
|
}
|
|
|
|
const policyTypeColor = (type: string) => {
|
|
switch (type) {
|
|
case 'car': return 'blue'
|
|
case 'life': return 'purple'
|
|
case 'fire': return 'orange'
|
|
default: return 'gray'
|
|
}
|
|
}
|
|
|
|
const formatDate = (date: string) => {
|
|
if (!date) return '—'
|
|
return new Date(date).toLocaleDateString('es-PA', {
|
|
day: '2-digit', month: 'short', year: 'numeric',
|
|
hour: '2-digit', minute: '2-digit'
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="p-8 space-y-8 bg-gray-50 min-h-screen">
|
|
<!-- Header -->
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<h1 class="text-3xl text-slate-900 font-bold">Workload</h1>
|
|
<p class="text-gray-500 text-sm">Quote & Solicitation Tasks</p>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<UBadge color="gray" variant="soft" size="lg">{{ total }} tasks</UBadge>
|
|
<NuxtLink to="/back-office/workload/kanban">
|
|
<UButton icon="i-heroicons-squares-2x2" color="gray" variant="soft">
|
|
Kanban View
|
|
</UButton>
|
|
</NuxtLink>
|
|
<UButton
|
|
icon="i-heroicons-arrow-path"
|
|
color="gray"
|
|
variant="soft"
|
|
:loading="pending"
|
|
@click="refresh()"
|
|
>
|
|
Refresh
|
|
</UButton>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="flex gap-4 items-center flex-wrap">
|
|
<USelect v-model="statusFilter" :items="statusItems" class="w-40" />
|
|
<USelect v-model="policyTypeFilter" :items="policyTypeItems" class="w-40" />
|
|
</div>
|
|
|
|
<UAlert
|
|
v-if="error"
|
|
color="red"
|
|
variant="soft"
|
|
title="Failed to load tasks"
|
|
:description="error.message"
|
|
/>
|
|
|
|
<div v-else-if="pending && tasks.length === 0" class="grid gap-4">
|
|
<UCard v-for="n in 5" :key="n">
|
|
<div class="h-20 animate-pulse bg-gray-200 rounded" />
|
|
</UCard>
|
|
</div>
|
|
|
|
<template v-else>
|
|
<div class="space-y-3" :class="pending ? 'opacity-60 pointer-events-none' : ''">
|
|
<NuxtLink
|
|
v-for="task in tasks"
|
|
:key="task.id"
|
|
:to="`/back-office/workload/${encodeURIComponent(task.id)}`"
|
|
>
|
|
<UCard class="hover:shadow-md transition-shadow cursor-pointer">
|
|
<div class="flex items-center justify-between gap-4">
|
|
<!-- Left -->
|
|
<div class="flex items-center gap-4 min-w-0">
|
|
<div class="flex flex-col gap-1">
|
|
<UBadge :color="statusColor(task.status)" variant="soft" size="xs">
|
|
{{ task.status }}
|
|
</UBadge>
|
|
<UBadge :color="policyTypeColor(task.task_info?.policy_type)" variant="outline" size="xs">
|
|
{{ task.task_info?.policy_type?.toUpperCase() || '—' }}
|
|
</UBadge>
|
|
</div>
|
|
|
|
<div class="min-w-0">
|
|
<p class="font-mono text-sm font-medium text-slate-800 truncate">
|
|
{{ task.application_id }}
|
|
</p>
|
|
<p class="text-xs text-gray-400">
|
|
Provider: <span class="font-mono">{{ task.provider_id }}</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right -->
|
|
<div class="flex items-center gap-6 flex-shrink-0 text-sm text-gray-500">
|
|
<div class="text-right">
|
|
<p class="text-xs text-gray-400">Received</p>
|
|
<p>{{ formatDate(task.created_at) }}</p>
|
|
</div>
|
|
<UIcon name="i-heroicons-chevron-right" class="w-4 h-4 text-gray-400" />
|
|
</div>
|
|
</div>
|
|
</UCard>
|
|
</NuxtLink>
|
|
|
|
<div v-if="tasks.length === 0 && !pending" class="text-center py-16 text-gray-400">
|
|
<UIcon name="i-heroicons-inbox" class="w-12 h-12 mx-auto mb-4" />
|
|
<p class="lg font-medium">No tasks found</p>
|
|
<p class="text-sm">Adjust your filters or wait for new requests</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<div v-if="totalPages > 1" class="flex justify-center">
|
|
<UPagination
|
|
v-model="page"
|
|
:total="total"
|
|
:page-count="20"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|