Merge branch 'KIN-UI-026-frontend_dev'
This commit is contained in:
commit
4b4ea1b02e
1 changed files with 53 additions and 4 deletions
|
|
@ -22,6 +22,33 @@ const filteredProjects = computed(() => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const SORT_OPTIONS = ['name', 'priority', 'active', 'cost'] as const
|
||||||
|
type SortOption = typeof SORT_OPTIONS[number]
|
||||||
|
const selectedSort = ref<SortOption>('name')
|
||||||
|
const showSortMenu = ref(false)
|
||||||
|
|
||||||
|
const sortedProjects = computed(() => {
|
||||||
|
const list = [...filteredProjects.value]
|
||||||
|
switch (selectedSort.value) {
|
||||||
|
case 'priority': return list.sort((a, b) => b.priority - a.priority)
|
||||||
|
case 'active': return list.sort((a, b) => b.active_tasks - a.active_tasks)
|
||||||
|
case 'cost': return list.sort((a, b) => (costMap.value[b.id] || 0) - (costMap.value[a.id] || 0))
|
||||||
|
default: return list.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const GROUP_KEYS = ['development', 'operations', 'research'] as const
|
||||||
|
|
||||||
|
const groupedProjects = computed(() => {
|
||||||
|
const groups: Record<string, Project[]> = { development: [], operations: [], research: [] }
|
||||||
|
for (const p of sortedProjects.value) {
|
||||||
|
const ptype = p.project_type || 'development'
|
||||||
|
const key = ptype in groups ? ptype : 'development'
|
||||||
|
groups[key].push(p)
|
||||||
|
}
|
||||||
|
return groups
|
||||||
|
})
|
||||||
|
|
||||||
// Add project modal
|
// Add project modal
|
||||||
const showAdd = ref(false)
|
const showAdd = ref(false)
|
||||||
const form = ref({
|
const form = ref({
|
||||||
|
|
@ -227,17 +254,36 @@ async function createNewProject() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!loading && !error" class="mb-3">
|
<div v-if="!loading && !error" class="mb-3 flex gap-2">
|
||||||
<input v-model="projectSearch"
|
<input v-model="projectSearch"
|
||||||
:placeholder="t('dashboard.search_placeholder')"
|
:placeholder="t('dashboard.search_placeholder')"
|
||||||
class="w-full bg-gray-800 border border-gray-700 rounded px-3 py-1.5 text-sm text-gray-300 placeholder-gray-600 focus:border-gray-500 outline-none" />
|
class="flex-1 bg-gray-800 border border-gray-700 rounded px-3 py-1.5 text-sm text-gray-300 placeholder-gray-600 focus:border-gray-500 outline-none" />
|
||||||
|
<div class="relative">
|
||||||
|
<div v-if="showSortMenu" class="fixed inset-0 z-[5]" @click="showSortMenu = false"></div>
|
||||||
|
<button @click="showSortMenu = !showSortMenu"
|
||||||
|
class="px-3 py-1.5 text-xs bg-gray-800 text-gray-400 border border-gray-700 rounded hover:bg-gray-700 flex items-center gap-1 whitespace-nowrap">
|
||||||
|
↕ {{ selectedSort }} ▾
|
||||||
|
</button>
|
||||||
|
<div v-if="showSortMenu" class="absolute right-0 top-full mt-1 bg-gray-900 border border-gray-700 rounded shadow-lg z-10 min-w-[100px]">
|
||||||
|
<button v-for="opt in SORT_OPTIONS" :key="opt"
|
||||||
|
@click="selectedSort = opt; showSortMenu = false"
|
||||||
|
class="w-full text-left px-3 py-2 text-xs hover:bg-gray-800"
|
||||||
|
:class="selectedSort === opt ? 'text-blue-400' : 'text-gray-400'">
|
||||||
|
{{ opt }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="loading" class="text-gray-500 text-sm">{{ t('dashboard.loading') }}</p>
|
<p v-if="loading" class="text-gray-500 text-sm">{{ t('dashboard.loading') }}</p>
|
||||||
<p v-else-if="error" class="text-red-400 text-sm">{{ error }}</p>
|
<p v-else-if="error" class="text-red-400 text-sm">{{ error }}</p>
|
||||||
|
|
||||||
<div v-else class="grid gap-3">
|
<div v-else class="space-y-4">
|
||||||
<div v-for="p in filteredProjects" :key="p.id">
|
<template v-for="groupKey in GROUP_KEYS" :key="groupKey">
|
||||||
|
<div v-if="groupedProjects[groupKey].length > 0">
|
||||||
|
<p class="text-xs text-gray-600 mb-2 text-center tracking-wider">── {{ groupKey }} ({{ groupedProjects[groupKey].length }}) ──</p>
|
||||||
|
<div class="grid gap-3">
|
||||||
|
<div v-for="p in groupedProjects[groupKey]" :key="p.id">
|
||||||
<!-- Inline delete confirmation -->
|
<!-- Inline delete confirmation -->
|
||||||
<div v-if="confirmDeleteId === p.id"
|
<div v-if="confirmDeleteId === p.id"
|
||||||
class="border border-red-800 rounded-lg p-4 bg-red-950/20">
|
class="border border-red-800 rounded-lg p-4 bg-red-950/20">
|
||||||
|
|
@ -305,6 +351,9 @@ async function createNewProject() {
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Add Project Modal -->
|
<!-- Add Project Modal -->
|
||||||
<Modal v-if="showAdd" :title="t('dashboard.add_project_title')" @close="showAdd = false">
|
<Modal v-if="showAdd" :title="t('dashboard.add_project_title')" @close="showAdd = false">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue