fix(routines): keep the daily desk for internal team work
Customer-linked tasks stay on their record and no longer appear in today, calendar, score, or reminders. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -23,7 +23,6 @@ import {
|
||||
Typography,
|
||||
} from 'ant-design-vue';
|
||||
import JalaliDateInput from '@/components/JalaliDateInput.vue';
|
||||
import EntityLinkPicker, { type PickedEntity } from '@/components/routines/EntityLinkPicker.vue';
|
||||
import { api } from '@/lib/api';
|
||||
import {
|
||||
ENTITY_TYPE_LABELS,
|
||||
@@ -64,7 +63,6 @@ const form = reactive({
|
||||
categoryId: '',
|
||||
assigneeId: '',
|
||||
isMandatory: false,
|
||||
entity: null as PickedEntity | null,
|
||||
});
|
||||
const saving = ref(false);
|
||||
const error = ref('');
|
||||
@@ -110,7 +108,6 @@ watch(
|
||||
categoryId: '',
|
||||
assigneeId: props.defaultAssigneeId || auth.user?.id || '',
|
||||
isMandatory: false,
|
||||
entity: null,
|
||||
});
|
||||
error.value = '';
|
||||
void ensureContext();
|
||||
@@ -138,14 +135,14 @@ async function submit() {
|
||||
error.value = 'عنوان کار را وارد کنید.';
|
||||
return Promise.reject();
|
||||
}
|
||||
const link = props.preset ?? form.entity;
|
||||
const link = props.preset;
|
||||
const body: CreateRoutineTaskRequest = {
|
||||
title: form.title.trim(),
|
||||
description: form.description.trim() || null,
|
||||
priority: form.priority,
|
||||
categoryId: form.categoryId || null,
|
||||
assigneeId: form.assigneeId || null,
|
||||
isMandatory: form.isMandatory,
|
||||
isMandatory: link ? false : form.isMandatory,
|
||||
source: sourceFor(link?.type),
|
||||
scheduledFor: form.date || todayKey(),
|
||||
dueAt: form.time ? localInputToIso(form.date || todayKey(), form.time) : null,
|
||||
@@ -182,7 +179,7 @@ async function submit() {
|
||||
<a-form-item label="عنوان" required>
|
||||
<a-input
|
||||
v-model:value="form.title"
|
||||
placeholder="مثلاً: تماس با مشتری برای پیگیری پیشفاکتور"
|
||||
placeholder="مثلاً: آمادهسازی گزارش هفتگی تیم"
|
||||
:maxlength="200"
|
||||
/>
|
||||
</a-form-item>
|
||||
@@ -229,16 +226,20 @@ async function submit() {
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="اتصال به پرونده">
|
||||
<a-space v-if="preset" :size="8">
|
||||
<a-col v-if="preset" :span="24">
|
||||
<a-form-item label="پرونده">
|
||||
<a-space :size="8">
|
||||
<a-tag :bordered="false">{{ ENTITY_TYPE_LABELS[preset.type] }}</a-tag>
|
||||
<a-typography-text strong>{{ preset.label }}</a-typography-text>
|
||||
</a-space>
|
||||
<EntityLinkPicker v-else v-model="form.entity" />
|
||||
</a-form-item>
|
||||
<a-alert
|
||||
type="info"
|
||||
show-icon
|
||||
message="این کار روی همین پرونده میماند و در برنامه روزانه کارهای روتین دیده نمیشود."
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-col v-else :span="24">
|
||||
<a-checkbox v-model:checked="form.isMandatory">
|
||||
کار اجباری (در امتیاز روزانه و Streak حساب میشود)
|
||||
</a-checkbox>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import {
|
||||
Alert as AAlert,
|
||||
Button as AButton,
|
||||
@@ -19,7 +18,14 @@ import RoutineTaskRow, { type RoutineTaskRowAction } from '@/components/routines
|
||||
import RoutineTaskModals from '@/components/routines/RoutineTaskModals.vue';
|
||||
import { api } from '@/lib/api';
|
||||
import { toFaDigits } from '@/lib/jalali';
|
||||
import { apiError, isOpenStatus } from '@/lib/routines';
|
||||
import {
|
||||
ROUTINE_TASK_STATUS_LABELS,
|
||||
apiError,
|
||||
formatDayNumeric,
|
||||
formatTime,
|
||||
hasExplicitTime,
|
||||
isOpenStatus,
|
||||
} from '@/lib/routines';
|
||||
import { useRoutineTaskActions } from '@/lib/useRoutineTaskActions';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
|
||||
@@ -38,7 +44,6 @@ const props = withDefaults(
|
||||
},
|
||||
);
|
||||
|
||||
const router = useRouter();
|
||||
const auth = useAuthStore();
|
||||
|
||||
const enabled = computed(() => {
|
||||
@@ -51,6 +56,7 @@ const loading = ref(false);
|
||||
const error = ref('');
|
||||
const showClosed = ref(false);
|
||||
const quickOpen = ref(false);
|
||||
const detail = ref<RoutineTaskDto | null>(null);
|
||||
|
||||
async function load() {
|
||||
if (!enabled.value || !props.entity.id) return;
|
||||
@@ -73,6 +79,7 @@ watch(() => `${props.entity.type}:${props.entity.id}`, load, { immediate: true }
|
||||
const actions = useRoutineTaskActions((updated) => {
|
||||
const i = items.value.findIndex((t) => t.id === updated.id);
|
||||
if (i >= 0) items.value[i] = updated;
|
||||
if (detail.value?.id === updated.id) detail.value = updated;
|
||||
});
|
||||
|
||||
function onAction(kind: RoutineTaskRowAction, task: RoutineTaskDto) {
|
||||
@@ -88,7 +95,7 @@ const open = computed(() => items.value.filter((t) => isOpenStatus(t.status)));
|
||||
const closed = computed(() => items.value.filter((t) => !isOpenStatus(t.status)));
|
||||
|
||||
function openTask(t: RoutineTaskDto) {
|
||||
void router.push({ path: '/routines', query: { task: t.id } });
|
||||
detail.value = t;
|
||||
}
|
||||
|
||||
function onCreated(t: RoutineTaskDto) {
|
||||
@@ -182,5 +189,27 @@ function onCreated(t: RoutineTaskDto) {
|
||||
|
||||
<QuickTaskModal :open="quickOpen" :preset="entity" @close="quickOpen = false" @created="onCreated" />
|
||||
<RoutineTaskModals :actions="actions" />
|
||||
<a-drawer :open="detail != null" title="جزئیات کار" @close="detail = null">
|
||||
<template v-if="detail">
|
||||
<a-typography-paragraph v-if="detail.description">{{ detail.description }}</a-typography-paragraph>
|
||||
<a-descriptions :column="1">
|
||||
<a-descriptions-item label="وضعیت">
|
||||
{{ ROUTINE_TASK_STATUS_LABELS[detail.status] }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="مسئول">
|
||||
{{ detail.assignee?.fullName || '—' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="روز برنامه">
|
||||
{{ formatDayNumeric(detail.scheduledFor) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="مهلت">
|
||||
{{ detail.dueAt && hasExplicitTime(detail) ? formatTime(detail.dueAt) : 'تا پایان روز' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
<a-typography-paragraph v-if="detail.resultNote" :style="{ marginTop: '12px' }">
|
||||
{{ detail.resultNote }}
|
||||
</a-typography-paragraph>
|
||||
</template>
|
||||
</a-drawer>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
@@ -97,6 +97,11 @@ const actions = useRoutineTaskActions((updated) => {
|
||||
});
|
||||
|
||||
const isOpen = computed(() => (task.value ? isOpenStatus(task.value.status) : false));
|
||||
const customerTask = computed(() => {
|
||||
const t = task.value;
|
||||
if (!t) return false;
|
||||
return (t.source !== 'routine' && t.source !== 'manual') || t.entities.length > 0;
|
||||
});
|
||||
const done = computed(() => task.value?.status === 'completed');
|
||||
const canManage = computed(() => !!shell?.context.value?.isManager);
|
||||
const priorities = Object.entries(PRIORITY_LABELS) as Array<[ActivityPriority, string]>;
|
||||
@@ -190,10 +195,13 @@ function eventText(ev: RoutineTaskEventDto): string {
|
||||
</div>
|
||||
<h3 class="td-title" :class="{ done }">{{ task.title }}</h3>
|
||||
<p v-if="task.description" class="td-desc">{{ task.description }}</p>
|
||||
<p v-if="customerTask" class="note">
|
||||
این کار مربوط به پرونده است و در برنامه روزانه کارهای روتین نیست.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- اکشن اصلی -->
|
||||
<section v-if="!editing" class="td-actions">
|
||||
<section v-if="!editing && !customerTask" class="td-actions">
|
||||
<UiButton
|
||||
v-if="isOpen"
|
||||
variant="success"
|
||||
@@ -370,7 +378,7 @@ function eventText(ev: RoutineTaskEventDto): string {
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<template v-if="task && !editing" #footer>
|
||||
<template v-if="task && !editing && !customerTask" #footer>
|
||||
<UiButton variant="ghost" size="sm" @click="startEdit">
|
||||
<template #icon><UiIcon name="pencil" :size="14" /></template>
|
||||
ویرایش
|
||||
|
||||
@@ -296,29 +296,6 @@ async function remove() {
|
||||
}
|
||||
}
|
||||
|
||||
const routinesEnabled = computed(() => {
|
||||
const mods = auth.user?.enabledModules;
|
||||
return !mods || mods.includes('routines');
|
||||
});
|
||||
const converting = ref(false);
|
||||
|
||||
/** پیگیری → Task در «کارهای روتین» (تکراری نمیسازد) */
|
||||
async function convertToTask() {
|
||||
if (!id.value || converting.value) return;
|
||||
converting.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
const { data } = await api.post<{ id: string }>(`/routine-tasks/from-activity/${id.value}`);
|
||||
moreOpen.value = false;
|
||||
void router.push({ path: '/routines', query: { task: data.id } });
|
||||
} catch (e) {
|
||||
const msg = (e as { response?: { data?: { message?: string | string[] } } }).response?.data?.message;
|
||||
error.value = Array.isArray(msg) ? msg.join('، ') : (msg ?? 'تبدیل به Task ممکن نشد.');
|
||||
} finally {
|
||||
converting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch(id, () => {
|
||||
commentOpen.value = false;
|
||||
postponeOpen.value = false;
|
||||
@@ -344,9 +321,6 @@ onMounted(() => {
|
||||
</button>
|
||||
<div v-if="moreOpen && activity" class="menu">
|
||||
<RouterLink :to="{ name: 'activity-edit', params: { id } }">ویرایش</RouterLink>
|
||||
<button v-if="routinesEnabled" type="button" :disabled="converting" @click="convertToTask">
|
||||
تبدیل به Task
|
||||
</button>
|
||||
<button type="button" class="danger" @click="remove">حذف</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -101,7 +101,7 @@ const hideHeader = computed(() => route.name === 'routine-new' || route.name ===
|
||||
<template>
|
||||
<section class="crm-page routines-shell">
|
||||
<template v-if="!hideHeader">
|
||||
<UiPageHeader title="کارهای روتین" lead="برنامه روزانه، روتینهای تکرارشونده و پیگیری انجام کارها">
|
||||
<UiPageHeader title="کارهای روتین" lead="برنامه روزانه و کارهای داخلی تیم">
|
||||
<template #actions>
|
||||
<RouterLink to="/routines/settings" class="icon-link" title="تنظیمات یادآوری و اعلانها">
|
||||
<UiIcon name="bell" :size="17" />
|
||||
|
||||
@@ -51,7 +51,9 @@ const f = reactive({
|
||||
const users = computed(() => shell.context.value?.users ?? []);
|
||||
const teams = computed(() => shell.context.value?.teams ?? []);
|
||||
const categories = computed(() => shell.context.value?.categories ?? []);
|
||||
const sources = Object.entries(ROUTINE_TASK_SOURCE_LABELS) as Array<[RoutineTaskSource, string]>;
|
||||
const sources = (['routine', 'manual'] as const).map(
|
||||
(key) => [key, ROUTINE_TASK_SOURCE_LABELS[key]] as [RoutineTaskSource, string],
|
||||
);
|
||||
const statuses = Object.entries(ROUTINE_TASK_STATUS_LABELS) as Array<[RoutineTaskStatus, string]>;
|
||||
|
||||
const query = computed<RoutineStatsQuery>(() => {
|
||||
|
||||
Reference in New Issue
Block a user