Files
CRM_Montzar/apps/web/src/components/routines/RoutineTaskRow.vue
T

199 lines
6.1 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { computed } from 'vue';
import { RouterLink } from 'vue-router';
import type { RoutineTaskDto } from '@mento/shared';
import {
Button as AButton,
Checkbox as ACheckbox,
Dropdown as ADropdown,
Flex as AFlex,
Menu as AMenu,
Space as ASpace,
Tag as ATag,
Typography,
} from 'ant-design-vue';
import {
CalendarOutlined,
CheckOutlined,
ClockCircleOutlined,
FlagOutlined,
MoreOutlined,
WarningOutlined,
} from '@ant-design/icons-vue';
const AMenuItem = AMenu.Item;
import UserAvatar from '@/components/UserAvatar.vue';
import {
ENTITY_TYPE_LABELS,
PRIORITY_LABELS,
ROUTINE_TASK_SOURCE_LABELS,
ROUTINE_TASK_STATUS_LABELS,
entityRoute,
formatDayNumeric,
formatDelay,
formatTime,
hasExplicitTime,
isOpenStatus,
} from '@/lib/routines';
const { Text: ATypographyText, Title: ATypographyTitle, Paragraph: ATypographyParagraph } =
Typography;
export type RoutineTaskRowAction =
| 'start'
| 'complete-note'
| 'reschedule'
| 'move-today'
| 'skip'
| 'cancel';
const props = withDefaults(
defineProps<{
task: RoutineTaskDto;
busy?: boolean;
showAssignee?: boolean;
showDate?: boolean;
showDelay?: boolean;
}>(),
{ busy: false, showAssignee: false, showDate: false, showDelay: false },
);
const emit = defineEmits<{
toggle: [task: RoutineTaskDto];
open: [task: RoutineTaskDto];
action: [kind: RoutineTaskRowAction, task: RoutineTaskDto];
}>();
const done = computed(() => props.task.status === 'completed');
const closed = computed(() => props.task.status === 'skipped' || props.task.status === 'cancelled');
const open = computed(() => isOpenStatus(props.task.status));
const showTime = computed(() => hasExplicitTime(props.task));
const checkLabel = computed(() =>
done.value
? `برگرداندن «${props.task.title}» به حالت انجام‌نشده`
: `علامت‌زدن «${props.task.title}» به‌عنوان انجام‌شده`,
);
function act(kind: RoutineTaskRowAction) {
emit('action', kind, props.task);
}
function onMenuClick({ key }: { key: string | number }) {
const k = String(key);
if (k === 'open') {
emit('open', props.task);
return;
}
act(k as RoutineTaskRowAction);
}
</script>
<template>
<a-flex
align="flex-start"
:gap="12"
:style="{
width: '100%',
padding: '12px 16px',
border: '1px solid var(--ant-color-border-secondary, #f0f0f0)',
borderRadius: '8px',
cursor: 'pointer',
opacity: closed ? 0.62 : 1,
}"
@click="emit('open', task)"
>
<a-checkbox
:checked="done"
:disabled="busy || closed"
:aria-label="checkLabel"
@click.stop
@update:checked="emit('toggle', task)"
/>
<a-flex vertical :gap="4" style="flex: 1; min-width: 0">
<a-space :size="6">
<a-typography-title
:level="5"
:style="{
margin: 0,
textDecoration: done ? 'line-through' : undefined,
}"
>
{{ task.title }}
</a-typography-title>
<FlagOutlined v-if="task.isMandatory" />
</a-space>
<a-typography-paragraph
v-if="task.description"
type="secondary"
:ellipsis="{ rows: 1 }"
:style="{ marginBottom: 0 }"
>
{{ task.description }}
</a-typography-paragraph>
<a-space wrap :size="[8, 4]">
<a-typography-text v-if="showDate" type="secondary">
<CalendarOutlined /> {{ formatDayNumeric(task.scheduledFor) }}
</a-typography-text>
<a-typography-text v-if="showTime" type="secondary">
<ClockCircleOutlined /> {{ formatTime(task.dueAt) }}
</a-typography-text>
<a-typography-text
v-if="showDelay && task.status === 'overdue'"
type="danger"
>
<WarningOutlined /> {{ formatDelay(task.dueAt) }} تأخیر
</a-typography-text>
<a-typography-text v-if="done && task.completedAt" type="success">
<CheckOutlined /> {{ formatTime(task.completedAt) }}
</a-typography-text>
<a-tag v-if="task.category" :bordered="false">{{ task.category.name }}</a-tag>
<a-tag :bordered="false">{{ PRIORITY_LABELS[task.priority] }}</a-tag>
<a-tag v-if="task.status !== 'todo'" :bordered="false">
{{ ROUTINE_TASK_STATUS_LABELS[task.status] }}
</a-tag>
<a-tag :bordered="false">{{ ROUTINE_TASK_SOURCE_LABELS[task.source] }}</a-tag>
<RouterLink
v-for="ent in task.entities"
:key="`${ent.type}-${ent.id}`"
:to="entityRoute(ent)"
@click.stop
>
<a-tag color="processing" :bordered="false">
{{ ENTITY_TYPE_LABELS[ent.type] }} · {{ ent.label }}
</a-tag>
</RouterLink>
</a-space>
</a-flex>
<a-space :size="8" @click.stop>
<a-space v-if="showAssignee && task.assignee" :size="6">
<UserAvatar :name="task.assignee.fullName" :user-id="task.assignee.id" :size="26" />
<a-typography-text type="secondary">{{ task.assignee.fullName }}</a-typography-text>
</a-space>
<a-dropdown>
<a-button type="text" aria-label="عملیات بیشتر">
<template #icon><MoreOutlined /></template>
</a-button>
<template #overlay>
<a-menu @click="onMenuClick">
<a-menu-item key="open">جزئیات</a-menu-item>
<a-menu-item v-if="task.status === 'todo'" key="start">شروع کار</a-menu-item>
<a-menu-item v-if="open || done" key="complete-note">
{{ done ? 'ویرایش گزارش نتیجه' : 'انجام با ثبت گزارش' }}
</a-menu-item>
<a-menu-item v-if="open" key="reschedule">تغییر زمان</a-menu-item>
<a-menu-item v-if="task.status === 'overdue'" key="move-today">
انتقال به امروز
</a-menu-item>
<a-menu-item v-if="open && task.rules.allowSkip" key="skip">رد کردن</a-menu-item>
<a-menu-item v-if="open" key="cancel" danger>لغو کار</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-space>
</a-flex>
</template>