2026-10-22 11:53:26 +03:30
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { computed, ref, watch } from 'vue';
|
|
|
|
|
|
import type {
|
|
|
|
|
|
DataAccessOwnerKind,
|
|
|
|
|
|
DataAccessRule,
|
|
|
|
|
|
DataAccessSection,
|
|
|
|
|
|
PersonMood,
|
|
|
|
|
|
SectionDataAccess,
|
|
|
|
|
|
} from '@mento/shared';
|
|
|
|
|
|
import {
|
|
|
|
|
|
DATA_ACCESS_SECTION_LABELS,
|
|
|
|
|
|
PERSON_IMPORT_SOURCE_LABELS,
|
|
|
|
|
|
PERSON_IMPORT_SOURCES,
|
|
|
|
|
|
PERSON_MOOD_LABELS,
|
|
|
|
|
|
PERSON_MOODS,
|
|
|
|
|
|
sectionHasDataAccessLimits,
|
|
|
|
|
|
} from '@mento/shared';
|
|
|
|
|
|
import { UiButton } from '@/components/ui';
|
|
|
|
|
|
|
|
|
|
|
|
export type IdOption = { id: string; name: string };
|
|
|
|
|
|
|
|
|
|
|
|
const props = withDefaults(
|
|
|
|
|
|
defineProps<{
|
|
|
|
|
|
modelValue: SectionDataAccess;
|
|
|
|
|
|
section?: DataAccessSection;
|
|
|
|
|
|
groups?: IdOption[];
|
|
|
|
|
|
stages?: IdOption[];
|
|
|
|
|
|
pipelines?: IdOption[];
|
|
|
|
|
|
boards?: IdOption[];
|
|
|
|
|
|
activityTypes?: IdOption[];
|
|
|
|
|
|
disabled?: boolean;
|
|
|
|
|
|
compact?: boolean;
|
|
|
|
|
|
}>(),
|
|
|
|
|
|
{
|
|
|
|
|
|
section: 'customers',
|
|
|
|
|
|
groups: () => [],
|
|
|
|
|
|
stages: () => [],
|
|
|
|
|
|
pipelines: () => [],
|
|
|
|
|
|
boards: () => [],
|
|
|
|
|
|
activityTypes: () => [],
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
'update:modelValue': [SectionDataAccess];
|
|
|
|
|
|
}>();
|
|
|
|
|
|
|
|
|
|
|
|
const sectionLabel = computed(() => DATA_ACCESS_SECTION_LABELS[props.section]);
|
|
|
|
|
|
|
|
|
|
|
|
const showGroups = computed(() => props.section === 'customers');
|
|
|
|
|
|
const showMoods = computed(() => props.section === 'customers');
|
|
|
|
|
|
const showImportSources = computed(() => props.section === 'customers');
|
|
|
|
|
|
const showLeadSources = computed(() => props.section === 'leads');
|
|
|
|
|
|
const showNoteTypes = computed(() => props.section === 'notes');
|
|
|
|
|
|
const showTicketStatuses = computed(() => props.section === 'tickets');
|
|
|
|
|
|
const showStages = computed(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
props.section === 'leads' ||
|
|
|
|
|
|
props.section === 'sales' ||
|
|
|
|
|
|
props.section === 'activities' ||
|
|
|
|
|
|
props.section === 'projects',
|
|
|
|
|
|
);
|
|
|
|
|
|
const showPipelines = computed(() => props.section === 'sales');
|
|
|
|
|
|
const showBoards = computed(
|
|
|
|
|
|
() => props.section === 'activities' || props.section === 'projects',
|
|
|
|
|
|
);
|
|
|
|
|
|
const showTypes = computed(
|
|
|
|
|
|
() => props.section === 'activities' || props.section === 'routines',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const ownerSelfLabel = computed(() => {
|
|
|
|
|
|
if (
|
|
|
|
|
|
props.section === 'leads' ||
|
|
|
|
|
|
props.section === 'activities' ||
|
|
|
|
|
|
props.section === 'routines' ||
|
|
|
|
|
|
props.section === 'tickets'
|
|
|
|
|
|
) {
|
|
|
|
|
|
return 'فقط مسئول خودش';
|
|
|
|
|
|
}
|
|
|
|
|
|
if (props.section === 'notes') return 'فقط نویسنده خودش';
|
|
|
|
|
|
return 'فقط پروندههای خود';
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const noteTypeOptions = [
|
|
|
|
|
|
{ value: 'general', label: 'عمومی' },
|
|
|
|
|
|
{ value: 'call_result', label: 'نتیجه تماس' },
|
|
|
|
|
|
{ value: 'meeting', label: 'جلسه' },
|
|
|
|
|
|
{ value: 'important', label: 'مهم' },
|
|
|
|
|
|
{ value: 'warning', label: 'هشدار' },
|
|
|
|
|
|
{ value: 'next_action', label: 'اقدام بعدی' },
|
|
|
|
|
|
{ value: 'follow_up', label: 'پیگیری' },
|
|
|
|
|
|
{ value: 'idea', label: 'ایده' },
|
|
|
|
|
|
{ value: 'suggestion', label: 'پیشنهاد' },
|
|
|
|
|
|
{ value: 'personal', label: 'شخصی' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const ticketStatusOptions = [
|
|
|
|
|
|
{ value: 'open', label: 'باز' },
|
|
|
|
|
|
{ value: 'in_progress', label: 'در حال انجام' },
|
|
|
|
|
|
{ value: 'waiting', label: 'در انتظار' },
|
|
|
|
|
|
{ value: 'resolved', label: 'حلشده' },
|
|
|
|
|
|
{ value: 'closed', label: 'بسته' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const typesLegend = computed(() =>
|
|
|
|
|
|
props.section === 'routines' ? 'دسته روتین' : 'نوع فعالیت',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const mode = computed({
|
|
|
|
|
|
get: () => (props.modelValue?.mode === 'rules' ? 'rules' : 'all'),
|
|
|
|
|
|
set: (next: 'all' | 'rules') => {
|
|
|
|
|
|
if (next === 'all') {
|
|
|
|
|
|
emit('update:modelValue', { mode: 'all', rules: [] });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const rules = props.modelValue?.rules?.length
|
|
|
|
|
|
? props.modelValue.rules
|
|
|
|
|
|
: [emptyRule()];
|
|
|
|
|
|
emit('update:modelValue', { mode: 'rules', rules });
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const rules = computed(() => props.modelValue?.rules ?? []);
|
|
|
|
|
|
|
|
|
|
|
|
function emptyRule(): DataAccessRule {
|
|
|
|
|
|
return {};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function patchRule(index: number, patch: Partial<DataAccessRule>) {
|
|
|
|
|
|
const next = rules.value.map((r, i) => (i === index ? { ...r, ...patch } : { ...r }));
|
|
|
|
|
|
emit('update:modelValue', { mode: 'rules', rules: next });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function toggleOwner(index: number, kind: DataAccessOwnerKind) {
|
|
|
|
|
|
const current = new Set(rules.value[index]?.owners ?? []);
|
|
|
|
|
|
if (current.has(kind)) current.delete(kind);
|
|
|
|
|
|
else current.add(kind);
|
|
|
|
|
|
const owners = [...current] as DataAccessOwnerKind[];
|
|
|
|
|
|
patchRule(index, { owners: owners.length ? owners : undefined });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type ListKey =
|
|
|
|
|
|
| 'groupIds'
|
|
|
|
|
|
| 'sources'
|
|
|
|
|
|
| 'moods'
|
|
|
|
|
|
| 'stageIds'
|
|
|
|
|
|
| 'pipelineIds'
|
|
|
|
|
|
| 'boardIds'
|
|
|
|
|
|
| 'typeIds';
|
|
|
|
|
|
|
|
|
|
|
|
function toggleInList(index: number, key: ListKey, value: string) {
|
|
|
|
|
|
const rule = rules.value[index] ?? {};
|
|
|
|
|
|
const current = new Set((rule[key] as string[] | undefined) ?? []);
|
|
|
|
|
|
if (current.has(value)) current.delete(value);
|
|
|
|
|
|
else current.add(value);
|
|
|
|
|
|
const list = [...current];
|
|
|
|
|
|
patchRule(index, { [key]: list.length ? list : undefined });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function addRule() {
|
|
|
|
|
|
emit('update:modelValue', {
|
|
|
|
|
|
mode: 'rules',
|
|
|
|
|
|
rules: [...rules.value, emptyRule()],
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function removeRule(index: number) {
|
|
|
|
|
|
const next = rules.value.filter((_, i) => i !== index);
|
|
|
|
|
|
emit('update:modelValue', {
|
|
|
|
|
|
mode: next.length ? 'rules' : 'all',
|
|
|
|
|
|
rules: next,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function nameOf(list: IdOption[], id: string) {
|
|
|
|
|
|
return list.find((x) => x.id === id)?.name ?? id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function ruleSummary(rule: DataAccessRule): string {
|
|
|
|
|
|
const parts: string[] = [];
|
|
|
|
|
|
if (rule.owners?.includes('self')) parts.push(ownerSelfLabel.value.replace('فقط ', ''));
|
|
|
|
|
|
if (rule.owners?.includes('team')) parts.push('تیم');
|
|
|
|
|
|
if (rule.groupIds?.length) {
|
|
|
|
|
|
parts.push(
|
|
|
|
|
|
`گروه: ${rule.groupIds
|
|
|
|
|
|
.slice(0, 2)
|
|
|
|
|
|
.map((id) => nameOf(props.groups, id))
|
|
|
|
|
|
.join('، ')}${rule.groupIds.length > 2 ? '…' : ''}`,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (rule.sources?.length) {
|
|
|
|
|
|
const labelMap: Record<string, string> = {
|
|
|
|
|
|
...(PERSON_IMPORT_SOURCE_LABELS as Record<string, string>),
|
|
|
|
|
|
...Object.fromEntries(noteTypeOptions.map((o) => [o.value, o.label])),
|
|
|
|
|
|
...Object.fromEntries(ticketStatusOptions.map((o) => [o.value, o.label])),
|
|
|
|
|
|
};
|
|
|
|
|
|
const labels = rule.sources.map((s) => labelMap[s] ?? s).slice(0, 2);
|
|
|
|
|
|
const prefix =
|
|
|
|
|
|
props.section === 'notes'
|
|
|
|
|
|
? 'نوع'
|
|
|
|
|
|
: props.section === 'tickets'
|
|
|
|
|
|
? 'وضعیت'
|
|
|
|
|
|
: 'منبع';
|
|
|
|
|
|
parts.push(`${prefix}: ${labels.join('، ')}${rule.sources.length > 2 ? '…' : ''}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (rule.moods?.length) {
|
|
|
|
|
|
parts.push(
|
|
|
|
|
|
`حال: ${rule.moods.map((m) => PERSON_MOOD_LABELS[m as PersonMood] ?? m).join('، ')}`,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (rule.pipelineIds?.length) {
|
|
|
|
|
|
parts.push(
|
|
|
|
|
|
`پایپلاین: ${rule.pipelineIds
|
|
|
|
|
|
.slice(0, 2)
|
|
|
|
|
|
.map((id) => nameOf(props.pipelines, id))
|
|
|
|
|
|
.join('، ')}`,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (rule.stageIds?.length) {
|
|
|
|
|
|
parts.push(
|
|
|
|
|
|
`مرحله: ${rule.stageIds
|
|
|
|
|
|
.slice(0, 2)
|
|
|
|
|
|
.map((id) => nameOf(props.stages, id))
|
|
|
|
|
|
.join('، ')}`,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (rule.boardIds?.length) {
|
|
|
|
|
|
parts.push(
|
|
|
|
|
|
`بورد: ${rule.boardIds
|
|
|
|
|
|
.slice(0, 2)
|
|
|
|
|
|
.map((id) => nameOf(props.boards, id))
|
|
|
|
|
|
.join('، ')}`,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (rule.typeIds?.length) {
|
|
|
|
|
|
parts.push(
|
|
|
|
|
|
`نوع: ${rule.typeIds
|
|
|
|
|
|
.slice(0, 2)
|
|
|
|
|
|
.map((id) => nameOf(props.activityTypes, id))
|
|
|
|
|
|
.join('، ')}`,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
return parts.length ? parts.join(' · ') : 'قانون خالی (اثر ندارد)';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const sourceOptions = computed(() =>
|
|
|
|
|
|
PERSON_IMPORT_SOURCES.map((s) => ({
|
|
|
|
|
|
value: s,
|
|
|
|
|
|
label: PERSON_IMPORT_SOURCE_LABELS[s],
|
|
|
|
|
|
})),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const hasLimits = computed(() => sectionHasDataAccessLimits(props.modelValue));
|
|
|
|
|
|
|
|
|
|
|
|
const customSource = ref('');
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => props.modelValue,
|
|
|
|
|
|
() => {
|
|
|
|
|
|
customSource.value = '';
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
function addCustomSource(index: number) {
|
|
|
|
|
|
const v = customSource.value.trim();
|
|
|
|
|
|
if (!v) return;
|
|
|
|
|
|
toggleInList(index, 'sources', v);
|
|
|
|
|
|
customSource.value = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="dae" :class="{ compact }">
|
|
|
|
|
|
<div class="dae-mode">
|
|
|
|
|
|
<label>
|
|
|
|
|
|
<input v-model="mode" type="radio" value="all" :disabled="disabled" />
|
|
|
|
|
|
همهٔ {{ sectionLabel }} (بدون محدودیت عمقی)
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label>
|
|
|
|
|
|
<input v-model="mode" type="radio" value="rules" :disabled="disabled" />
|
2026-10-22 12:18:05 +03:30
|
|
|
|
محدود به قوانین زیر
|
2026-10-22 11:53:26 +03:30
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<template v-if="mode === 'rules'">
|
|
|
|
|
|
<p class="hint">
|
2026-10-22 12:18:05 +03:30
|
|
|
|
هر قانون یک راه جدا برای دیدن داده است. اگر چند قانون بگذارید، کافی است
|
|
|
|
|
|
<strong>یکی از آنها</strong> برقرار باشد تا رکورد دیده شود.
|
|
|
|
|
|
داخل یک قانون، گزینههایی که تیک میزنید باید
|
|
|
|
|
|
<strong>همه با هم</strong> برقرار باشند.
|
|
|
|
|
|
مثال: «پروندههای خودم» بهعنوان قانون ۱ و «گروه VIP» بهعنوان قانون ۲ → هم مال خودش را میبیند، هم VIP را.
|
2026-10-22 11:53:26 +03:30
|
|
|
|
</p>
|
|
|
|
|
|
<article v-for="(rule, index) in rules" :key="index" class="dae-rule">
|
|
|
|
|
|
<header>
|
|
|
|
|
|
<strong>قانون {{ index + 1 }}</strong>
|
|
|
|
|
|
<span class="muted">{{ ruleSummary(rule) }}</span>
|
2026-10-22 12:18:05 +03:30
|
|
|
|
<UiButton
|
|
|
|
|
|
v-if="!disabled"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
variant="secondary"
|
|
|
|
|
|
@click.prevent="removeRule(index)"
|
|
|
|
|
|
>
|
2026-10-22 11:53:26 +03:30
|
|
|
|
حذف
|
|
|
|
|
|
</UiButton>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<fieldset :disabled="disabled">
|
|
|
|
|
|
<legend>مسئول / مالک</legend>
|
|
|
|
|
|
<label class="chip">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="rule.owners?.includes('self')"
|
|
|
|
|
|
@change="toggleOwner(index, 'self')"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{{ ownerSelfLabel }}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="chip">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="rule.owners?.includes('team')"
|
|
|
|
|
|
@change="toggleOwner(index, 'team')"
|
|
|
|
|
|
/>
|
|
|
|
|
|
تیمهای عضو
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
|
|
|
|
|
|
<fieldset v-if="showGroups" :disabled="disabled">
|
|
|
|
|
|
<legend>گروهها</legend>
|
|
|
|
|
|
<p v-if="!groups.length" class="muted">گروهی تعریف نشده.</p>
|
|
|
|
|
|
<label v-for="g in groups" :key="g.id" class="chip">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="rule.groupIds?.includes(g.id)"
|
|
|
|
|
|
@change="toggleInList(index, 'groupIds', g.id)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{{ g.name }}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
|
|
|
|
|
|
<fieldset v-if="showImportSources" :disabled="disabled">
|
|
|
|
|
|
<legend>منبع</legend>
|
|
|
|
|
|
<label v-for="s in sourceOptions" :key="s.value" class="chip">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="rule.sources?.includes(s.value)"
|
|
|
|
|
|
@change="toggleInList(index, 'sources', s.value)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{{ s.label }}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<div class="custom-source">
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model="customSource"
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
placeholder="منبع سفارشی (sourceRef)…"
|
|
|
|
|
|
:disabled="disabled"
|
|
|
|
|
|
@keydown.enter.prevent="addCustomSource(index)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<UiButton v-if="!disabled" variant="secondary" @click="addCustomSource(index)">
|
|
|
|
|
|
افزودن
|
|
|
|
|
|
</UiButton>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
|
|
|
|
|
|
<fieldset v-if="showLeadSources" :disabled="disabled">
|
|
|
|
|
|
<legend>منبع لید</legend>
|
|
|
|
|
|
<div class="custom-source">
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model="customSource"
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
placeholder="مثلاً اینستاگرام، سایت…"
|
|
|
|
|
|
:disabled="disabled"
|
|
|
|
|
|
@keydown.enter.prevent="addCustomSource(index)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<UiButton v-if="!disabled" variant="secondary" @click="addCustomSource(index)">
|
|
|
|
|
|
افزودن
|
|
|
|
|
|
</UiButton>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<label v-for="s in rule.sources ?? []" :key="s" class="chip">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked
|
|
|
|
|
|
@change="toggleInList(index, 'sources', s)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{{ s }}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
|
|
|
|
|
|
<fieldset v-if="showNoteTypes" :disabled="disabled">
|
|
|
|
|
|
<legend>نوع یادداشت</legend>
|
|
|
|
|
|
<label v-for="t in noteTypeOptions" :key="t.value" class="chip">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="rule.sources?.includes(t.value)"
|
|
|
|
|
|
@change="toggleInList(index, 'sources', t.value)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{{ t.label }}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
|
|
|
|
|
|
<fieldset v-if="showTicketStatuses" :disabled="disabled">
|
|
|
|
|
|
<legend>وضعیت تیکت</legend>
|
|
|
|
|
|
<label v-for="t in ticketStatusOptions" :key="t.value" class="chip">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="rule.sources?.includes(t.value)"
|
|
|
|
|
|
@change="toggleInList(index, 'sources', t.value)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{{ t.label }}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
|
|
|
|
|
|
<fieldset v-if="showMoods" :disabled="disabled">
|
|
|
|
|
|
<legend>حال مشتری</legend>
|
|
|
|
|
|
<label v-for="m in PERSON_MOODS" :key="m" class="chip">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="rule.moods?.includes(m)"
|
|
|
|
|
|
@change="toggleInList(index, 'moods', m)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{{ PERSON_MOOD_LABELS[m] }}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
|
|
|
|
|
|
<fieldset v-if="showPipelines" :disabled="disabled">
|
|
|
|
|
|
<legend>پایپلاین</legend>
|
|
|
|
|
|
<p v-if="!pipelines.length" class="muted">پایپلاینی نیست.</p>
|
|
|
|
|
|
<label v-for="p in pipelines" :key="p.id" class="chip">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="rule.pipelineIds?.includes(p.id)"
|
|
|
|
|
|
@change="toggleInList(index, 'pipelineIds', p.id)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{{ p.name }}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
|
|
|
|
|
|
<fieldset v-if="showStages" :disabled="disabled">
|
|
|
|
|
|
<legend>مرحله</legend>
|
|
|
|
|
|
<p v-if="!stages.length" class="muted">مرحلهای نیست.</p>
|
|
|
|
|
|
<label v-for="s in stages" :key="s.id" class="chip">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="rule.stageIds?.includes(s.id)"
|
|
|
|
|
|
@change="toggleInList(index, 'stageIds', s.id)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{{ s.name }}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
|
|
|
|
|
|
<fieldset v-if="showBoards" :disabled="disabled">
|
|
|
|
|
|
<legend>بورد فعالیت</legend>
|
|
|
|
|
|
<p v-if="!boards.length" class="muted">بوردی نیست.</p>
|
|
|
|
|
|
<label v-for="b in boards" :key="b.id" class="chip">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="rule.boardIds?.includes(b.id)"
|
|
|
|
|
|
@change="toggleInList(index, 'boardIds', b.id)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{{ b.name }}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
|
|
|
|
|
|
<fieldset v-if="showTypes" :disabled="disabled">
|
|
|
|
|
|
<legend>{{ typesLegend }}</legend>
|
|
|
|
|
|
<p v-if="!activityTypes.length" class="muted">موردی تعریف نشده.</p>
|
|
|
|
|
|
<label v-for="t in activityTypes" :key="t.id" class="chip">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="rule.typeIds?.includes(t.id)"
|
|
|
|
|
|
@change="toggleInList(index, 'typeIds', t.id)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{{ t.name }}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
|
2026-10-22 12:18:05 +03:30
|
|
|
|
<UiButton
|
|
|
|
|
|
v-if="!disabled"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
variant="secondary"
|
|
|
|
|
|
@click.prevent="addRule"
|
|
|
|
|
|
>
|
|
|
|
|
|
+ قانون جدید
|
|
|
|
|
|
</UiButton>
|
2026-10-22 11:53:26 +03:30
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<p v-else-if="!hasLimits" class="hint">
|
|
|
|
|
|
بدون محدودیت عمقی — همهٔ {{ sectionLabel }} قابل مشاهدهاند.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.dae {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
.dae-mode {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.35rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
.dae-mode label,
|
|
|
|
|
|
.chip {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
|
font-size: 0.88rem;
|
|
|
|
|
|
margin-inline-end: 0.55rem;
|
|
|
|
|
|
margin-bottom: 0.25rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hint,
|
|
|
|
|
|
.muted {
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-size: 0.82rem;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.dae-rule {
|
|
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
padding: 0.75rem;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.65rem;
|
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
|
}
|
|
|
|
|
|
.dae-rule header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
}
|
|
|
|
|
|
fieldset {
|
|
|
|
|
|
border: 1px dashed #e2e8f0;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 0.5rem 0.65rem;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
legend {
|
|
|
|
|
|
padding-inline: 0.25rem;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
color: #475569;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.custom-source {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
|
margin-top: 0.35rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
.custom-source input {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 0.35rem 0.5rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
.compact .dae-rule {
|
|
|
|
|
|
padding: 0.55rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|