<script setup lang="ts">
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import type { Task } from '../api'
import { proposalItems, statusOptions } from '../taskui'
import TaskBadges from './TaskBadges.vue'
// Карточка задачи для списков (список, страница проекта): заголовок-ссылка
// на страницу задачи, сниппет описания/предложения ИИ, бейджи, «⋯»-меню.
// API-вызовы и подтверждение удаления — на родителе (эмитим события).
const props = defineProps<{
task: Task
/** Показывать бейдж проекта (скрывать на странице проекта) */
showProject?: boolean
}>()
const emit = defineEmits<{
'status-change': [task: Task, status: string]
delete: [task: Task]
}>()
const { t } = useI18n()
const router = useRouter()
// Сниппет: описание задачи, а без него — пункты предложения ИИ.
// Markdown-разметку убираем — сниппет plain-текст.
const snippet = computed(() => {
const text =
props.task.description.trim() || proposalItems(props.task).join(' · ')
return text
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
.replace(/\*\*([^*]+)\*\*/g, '$1')
.replace(/\*([^*]+)\*/g, '$1')
.replace(/`([^`]+)`/g, '$1')
})
function menu(task: Task) {
return [
...statusOptions().map((o) => ({
label: o.label,
icon: o.value === task.status ? 'ph-check' : undefined,
onSelect: () => emit('status-change', task, o.value),
})),
{ label: t('card.open'), icon: 'ph-arrow-square-out', onSelect: () => open(task.id) },
{ label: t('common.delete'), icon: 'ph-trash', danger: true, onSelect: () => emit('delete', task) },
]
}
function open(id: number) {
void router.push(`/tasks/${id}`)
}
</script>
<template>
<GnCard class="task-card">
<div class="task-main">
<RouterLink class="title" :class="{ done: task.status === 'done' }" :to="`/tasks/${task.id}`">
{{ task.title }}
</RouterLink>
<GnDropdown class="task-menu" :items="menu(task)">
<template #trigger="{ toggle }">
<GnIconButton icon="ph-dots-three-outline" :label="t('common.edit')" @click="toggle" />
</template>
</GnDropdown>
</div>
<p v-if="snippet" class="snippet">{{ snippet }}</p>
<div class="task-meta">
<TaskBadges :task="task" :hide-project="!showProject" />
</div>
</GnCard>
</template>
<style scoped>
.task-main {
display: flex;
gap: 0.5rem;
align-items: center;
}
.title {
flex: 1;
min-width: 0;
font-weight: 600;
color: inherit;
text-decoration: none;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.title:hover {
color: var(--accent, #7aa2f7);
}
.title.done {
text-decoration: line-through;
color: var(--text-muted, #888);
}
.snippet {
margin: 0.25rem 0 0;
color: var(--text-muted, #a9b1d6);
font-size: 0.9em;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.task-menu {
flex: none;
}
.task-meta {
display: flex;
flex-wrap: wrap;
gap: 0.3rem;
align-items: center;
margin-top: 0.5rem;
}
</style>