feat(activities): split customer column, wider search, custom activity types with type-specific fields
- Table: separate assignee and customer columns; search by assignee and activity type - ActivityTypeDef model with per-type field definitions (text, select, checklist, participants, ...) - Default types incl. meeting subject/participants and task checklist; admins manage types - Participants see activities in their lists and get notified Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
import { computed, ref } from 'vue';
|
||||
import type {
|
||||
ActivityDto,
|
||||
ActivityStageColor,
|
||||
ActivityType,
|
||||
ActivityTypeDefDto,
|
||||
ActivityTypeIcon,
|
||||
} from '@mento/shared';
|
||||
import { ACTIVITY_TYPE_LABELS } from '@mento/shared';
|
||||
import { api } from '@/lib/api';
|
||||
import { PIPELINE_ACCENT } from '@/lib/pipeline-board';
|
||||
|
||||
export const BASE_TYPE_ICON: Record<ActivityType, ActivityTypeIcon> = {
|
||||
call: 'phone',
|
||||
meeting: 'users',
|
||||
task: 'check',
|
||||
note: 'chat',
|
||||
};
|
||||
|
||||
const BASE_TYPE_COLOR: Record<ActivityType, ActivityStageColor> = {
|
||||
call: 'green',
|
||||
meeting: 'purple',
|
||||
task: 'orange',
|
||||
note: 'blue',
|
||||
};
|
||||
|
||||
export const ACTIVITY_TYPE_ICON_LABELS: Record<ActivityTypeIcon, string> = {
|
||||
phone: 'تلفن',
|
||||
users: 'جمع',
|
||||
check: 'تیک',
|
||||
chat: 'گفتگو',
|
||||
mail: 'ایمیل',
|
||||
building: 'ساختمان',
|
||||
calendar: 'تقویم',
|
||||
star: 'ستاره',
|
||||
flag: 'پرچم',
|
||||
briefcase: 'کیف',
|
||||
document: 'سند',
|
||||
target: 'هدف',
|
||||
truck: 'ارسال',
|
||||
send: 'ارسال پیام',
|
||||
megaphone: 'بلندگو',
|
||||
pin: 'مکان',
|
||||
bell: 'زنگ',
|
||||
sparkle: 'درخشش',
|
||||
};
|
||||
|
||||
const types = ref<ActivityTypeDefDto[]>([]);
|
||||
const loaded = ref(false);
|
||||
let pending: Promise<void> | null = null;
|
||||
|
||||
/** انواع فعالیت سازمان (کش مشترک بین صفحات) */
|
||||
export function useActivityTypes() {
|
||||
async function load(force = false): Promise<void> {
|
||||
if (loaded.value && !force) return;
|
||||
if (pending && !force) return pending;
|
||||
pending = api
|
||||
.get<ActivityTypeDefDto[]>('/activities/types')
|
||||
.then(({ data }) => {
|
||||
types.value = data;
|
||||
loaded.value = true;
|
||||
})
|
||||
.catch(() => {
|
||||
if (!loaded.value) types.value = [];
|
||||
})
|
||||
.finally(() => {
|
||||
pending = null;
|
||||
});
|
||||
return pending;
|
||||
}
|
||||
|
||||
const active = computed(() => types.value.filter((t) => t.isActive));
|
||||
const byId = computed(() => new Map(types.value.map((t) => [t.id, t])));
|
||||
/** نوع اصلی هر نوع پایه */
|
||||
const systemOf = computed(() => {
|
||||
const out: Partial<Record<ActivityType, ActivityTypeDefDto>> = {};
|
||||
for (const t of types.value) if (t.isSystem && !out[t.baseType]) out[t.baseType] = t;
|
||||
return out;
|
||||
});
|
||||
|
||||
/** تعریف کامل نوع مؤثر یک فعالیت */
|
||||
function defOf(a: Pick<ActivityDto, 'type' | 'typeDef'>): ActivityTypeDefDto | null {
|
||||
if (a.typeDef?.id) return byId.value.get(a.typeDef.id) ?? null;
|
||||
return systemOf.value[a.type] ?? null;
|
||||
}
|
||||
|
||||
return { types, active, byId, systemOf, loaded, load, defOf };
|
||||
}
|
||||
|
||||
export type TypeView = { name: string; icon: ActivityTypeIcon; color: ActivityStageColor };
|
||||
|
||||
/** برچسب/آیکون/رنگ قابل نمایش برای یک فعالیت */
|
||||
export function activityTypeView(a: Pick<ActivityDto, 'type' | 'typeDef'>): TypeView {
|
||||
if (a.typeDef) {
|
||||
return { name: a.typeDef.name, icon: a.typeDef.icon, color: a.typeDef.color };
|
||||
}
|
||||
return {
|
||||
name: ACTIVITY_TYPE_LABELS[a.type],
|
||||
icon: BASE_TYPE_ICON[a.type],
|
||||
color: BASE_TYPE_COLOR[a.type],
|
||||
};
|
||||
}
|
||||
|
||||
export function typeColorStyle(color: ActivityStageColor) {
|
||||
const c = PIPELINE_ACCENT[color] ?? PIPELINE_ACCENT.green;
|
||||
return { background: c.soft, color: c.text };
|
||||
}
|
||||
|
||||
export function newFieldKey(): string {
|
||||
return `f_${Math.random().toString(36).slice(2, 10)}`;
|
||||
}
|
||||
Reference in New Issue
Block a user