fix(acl): simplify data access UI to a single filter
Drop multi-rule OR/AND editing; one all-or-filter form per section with clear hints.
This commit is contained in:
@@ -75,10 +75,10 @@ const ownerSelfLabel = computed(() => {
|
||||
props.section === 'routines' ||
|
||||
props.section === 'tickets'
|
||||
) {
|
||||
return 'فقط مسئول خودش';
|
||||
return 'مسئول خودش';
|
||||
}
|
||||
if (props.section === 'notes') return 'فقط نویسنده خودش';
|
||||
return 'فقط پروندههای خود';
|
||||
if (props.section === 'notes') return 'نویسنده خودش';
|
||||
return 'پروندههای خود';
|
||||
});
|
||||
|
||||
const noteTypeOptions = [
|
||||
@@ -106,6 +106,61 @@ const typesLegend = computed(() =>
|
||||
props.section === 'routines' ? 'دسته روتین' : 'نوع فعالیت',
|
||||
);
|
||||
|
||||
const boardsLegend = computed(() =>
|
||||
props.section === 'projects' ? 'بورد پروژه' : 'بورد فعالیت',
|
||||
);
|
||||
|
||||
/** چند قانون قدیمی → یک فیلتر (اتحاد گزینهها) برای ویرایش ساده */
|
||||
function collapseRules(rules: DataAccessRule[]): DataAccessRule {
|
||||
if (!rules.length) return {};
|
||||
if (rules.length === 1) return { ...rules[0]! };
|
||||
const owners = new Set<DataAccessOwnerKind>();
|
||||
const groupIds = new Set<string>();
|
||||
const sources = new Set<string>();
|
||||
const moods = new Set<PersonMood>();
|
||||
const stageIds = new Set<string>();
|
||||
const pipelineIds = new Set<string>();
|
||||
const boardIds = new Set<string>();
|
||||
const typeIds = new Set<string>();
|
||||
for (const r of rules) {
|
||||
for (const o of r.owners ?? []) owners.add(o);
|
||||
for (const id of r.groupIds ?? []) groupIds.add(id);
|
||||
for (const s of r.sources ?? []) sources.add(s);
|
||||
for (const m of r.moods ?? []) moods.add(m);
|
||||
for (const id of r.stageIds ?? []) stageIds.add(id);
|
||||
for (const id of r.pipelineIds ?? []) pipelineIds.add(id);
|
||||
for (const id of r.boardIds ?? []) boardIds.add(id);
|
||||
for (const id of r.typeIds ?? []) typeIds.add(id);
|
||||
}
|
||||
const out: DataAccessRule = {};
|
||||
if (owners.size) out.owners = [...owners];
|
||||
if (groupIds.size) out.groupIds = [...groupIds];
|
||||
if (sources.size) out.sources = [...sources];
|
||||
if (moods.size) out.moods = [...moods];
|
||||
if (stageIds.size) out.stageIds = [...stageIds];
|
||||
if (pipelineIds.size) out.pipelineIds = [...pipelineIds];
|
||||
if (boardIds.size) out.boardIds = [...boardIds];
|
||||
if (typeIds.size) out.typeIds = [...typeIds];
|
||||
return out;
|
||||
}
|
||||
|
||||
function ruleHasAnyFilter(rule: DataAccessRule): boolean {
|
||||
return Boolean(
|
||||
rule.owners?.length ||
|
||||
rule.groupIds?.length ||
|
||||
rule.sources?.length ||
|
||||
rule.moods?.length ||
|
||||
rule.stageIds?.length ||
|
||||
rule.pipelineIds?.length ||
|
||||
rule.boardIds?.length ||
|
||||
rule.typeIds?.length,
|
||||
);
|
||||
}
|
||||
|
||||
const hadMultipleRules = computed(
|
||||
() => (props.modelValue?.rules?.length ?? 0) > 1,
|
||||
);
|
||||
|
||||
const mode = computed({
|
||||
get: () => (props.modelValue?.mode === 'rules' ? 'rules' : 'all'),
|
||||
set: (next: 'all' | 'rules') => {
|
||||
@@ -113,30 +168,39 @@ const mode = computed({
|
||||
emit('update:modelValue', { mode: 'all', rules: [] });
|
||||
return;
|
||||
}
|
||||
const rules = props.modelValue?.rules?.length
|
||||
? props.modelValue.rules
|
||||
: [emptyRule()];
|
||||
emit('update:modelValue', { mode: 'rules', rules });
|
||||
const collapsed = collapseRules(props.modelValue?.rules ?? []);
|
||||
emit('update:modelValue', {
|
||||
mode: 'rules',
|
||||
rules: [ruleHasAnyFilter(collapsed) ? collapsed : {}],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const rules = computed(() => props.modelValue?.rules ?? []);
|
||||
const filter = computed(() => collapseRules(props.modelValue?.rules ?? []));
|
||||
|
||||
function emptyRule(): DataAccessRule {
|
||||
return {};
|
||||
function emitFilter(next: DataAccessRule) {
|
||||
const cleaned: DataAccessRule = {};
|
||||
if (next.owners?.length) cleaned.owners = [...next.owners];
|
||||
if (next.groupIds?.length) cleaned.groupIds = [...next.groupIds];
|
||||
if (next.sources?.length) cleaned.sources = [...next.sources];
|
||||
if (next.moods?.length) cleaned.moods = [...next.moods];
|
||||
if (next.stageIds?.length) cleaned.stageIds = [...next.stageIds];
|
||||
if (next.pipelineIds?.length) cleaned.pipelineIds = [...next.pipelineIds];
|
||||
if (next.boardIds?.length) cleaned.boardIds = [...next.boardIds];
|
||||
if (next.typeIds?.length) cleaned.typeIds = [...next.typeIds];
|
||||
emit('update:modelValue', { mode: 'rules', rules: [cleaned] });
|
||||
}
|
||||
|
||||
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 patchFilter(patch: Partial<DataAccessRule>) {
|
||||
emitFilter({ ...filter.value, ...patch });
|
||||
}
|
||||
|
||||
function toggleOwner(index: number, kind: DataAccessOwnerKind) {
|
||||
const current = new Set(rules.value[index]?.owners ?? []);
|
||||
function toggleOwner(kind: DataAccessOwnerKind) {
|
||||
const current = new Set(filter.value.owners ?? []);
|
||||
if (current.has(kind)) current.delete(kind);
|
||||
else current.add(kind);
|
||||
const owners = [...current] as DataAccessOwnerKind[];
|
||||
patchRule(index, { owners: owners.length ? owners : undefined });
|
||||
patchFilter({ owners: owners.length ? owners : undefined });
|
||||
}
|
||||
|
||||
type ListKey =
|
||||
@@ -148,99 +212,12 @@ type ListKey =
|
||||
| 'boardIds'
|
||||
| 'typeIds';
|
||||
|
||||
function toggleInList(index: number, key: ListKey, value: string) {
|
||||
const rule = rules.value[index] ?? {};
|
||||
const current = new Set((rule[key] as string[] | undefined) ?? []);
|
||||
function toggleInList(key: ListKey, value: string) {
|
||||
const current = new Set((filter.value[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(' · ') : 'قانون خالی (اثر ندارد)';
|
||||
patchFilter({ [key]: list.length ? list : undefined });
|
||||
}
|
||||
|
||||
const sourceOptions = computed(() =>
|
||||
@@ -260,12 +237,27 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
function addCustomSource(index: number) {
|
||||
function addCustomSource() {
|
||||
const v = customSource.value.trim();
|
||||
if (!v) return;
|
||||
toggleInList(index, 'sources', v);
|
||||
toggleInList('sources', v);
|
||||
customSource.value = '';
|
||||
}
|
||||
|
||||
const filterSummary = computed(() => {
|
||||
const rule = filter.value;
|
||||
const parts: string[] = [];
|
||||
if (rule.owners?.includes('self')) parts.push(ownerSelfLabel.value);
|
||||
if (rule.owners?.includes('team')) parts.push('تیمهای عضو');
|
||||
if (rule.groupIds?.length) parts.push(`${rule.groupIds.length} گروه`);
|
||||
if (rule.sources?.length) parts.push(`${rule.sources.length} مورد منبع/نوع`);
|
||||
if (rule.moods?.length) parts.push(`${rule.moods.length} حال`);
|
||||
if (rule.pipelineIds?.length) parts.push(`${rule.pipelineIds.length} پایپلاین`);
|
||||
if (rule.stageIds?.length) parts.push(`${rule.stageIds.length} مرحله`);
|
||||
if (rule.boardIds?.length) parts.push(`${rule.boardIds.length} بورد`);
|
||||
if (rule.typeIds?.length) parts.push(`${rule.typeIds.length} نوع/دسته`);
|
||||
return parts.length ? parts.join(' · ') : 'هنوز فیلتری انتخاب نشده';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -273,218 +265,204 @@ function addCustomSource(index: number) {
|
||||
<div class="dae-mode">
|
||||
<label>
|
||||
<input v-model="mode" type="radio" value="all" :disabled="disabled" />
|
||||
همهٔ {{ sectionLabel }} (بدون محدودیت عمقی)
|
||||
همهٔ {{ sectionLabel }}
|
||||
</label>
|
||||
<label>
|
||||
<input v-model="mode" type="radio" value="rules" :disabled="disabled" />
|
||||
محدود به قوانین زیر
|
||||
محدود با فیلتر
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<template v-if="mode === 'rules'">
|
||||
<p class="hint">
|
||||
هر قانون یک راه جدا برای دیدن داده است. اگر چند قانون بگذارید، کافی است
|
||||
<strong>یکی از آنها</strong> برقرار باشد تا رکورد دیده شود.
|
||||
داخل یک قانون، گزینههایی که تیک میزنید باید
|
||||
<strong>همه با هم</strong> برقرار باشند.
|
||||
مثال: «پروندههای خودم» بهعنوان قانون ۱ و «گروه VIP» بهعنوان قانون ۲ → هم مال خودش را میبیند، هم VIP را.
|
||||
فقط رکوردهایی دیده میشوند که با
|
||||
<strong>همهٔ فیلترهای پرشده</strong>
|
||||
جور باشند. در هر ردیف، چند تیک یعنی هر کدام کافی است.
|
||||
</p>
|
||||
<article v-for="(rule, index) in rules" :key="index" class="dae-rule">
|
||||
<header>
|
||||
<strong>قانون {{ index + 1 }}</strong>
|
||||
<span class="muted">{{ ruleSummary(rule) }}</span>
|
||||
<p v-if="hadMultipleRules" class="hint warn">
|
||||
تنظیم قبلی چندتکه بود؛ اینجا در یک فیلتر ادغام شده. با ذخیره به همین شکل ساده ثبت میشود.
|
||||
</p>
|
||||
<p class="summary">{{ filterSummary }}</p>
|
||||
|
||||
<fieldset :disabled="disabled">
|
||||
<legend>مالک / مسئول</legend>
|
||||
<p class="muted">خالی = بدون محدودیت مالک</p>
|
||||
<label class="chip">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="filter.owners?.includes('self')"
|
||||
@change="toggleOwner('self')"
|
||||
/>
|
||||
{{ ownerSelfLabel }}
|
||||
</label>
|
||||
<label class="chip">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="filter.owners?.includes('team')"
|
||||
@change="toggleOwner('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="filter.groupIds?.includes(g.id)"
|
||||
@change="toggleInList('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="filter.sources?.includes(s.value)"
|
||||
@change="toggleInList('sources', s.value)"
|
||||
/>
|
||||
{{ s.label }}
|
||||
</label>
|
||||
<div class="custom-source">
|
||||
<input
|
||||
v-model="customSource"
|
||||
type="text"
|
||||
placeholder="منبع سفارشی…"
|
||||
:disabled="disabled"
|
||||
@keydown.enter.prevent="addCustomSource"
|
||||
/>
|
||||
<UiButton
|
||||
v-if="!disabled"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
@click.prevent="removeRule(index)"
|
||||
@click.prevent="addCustomSource"
|
||||
>
|
||||
حذف
|
||||
افزودن
|
||||
</UiButton>
|
||||
</header>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<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="showLeadSources" :disabled="disabled">
|
||||
<legend>منبع لید</legend>
|
||||
<div class="custom-source">
|
||||
<input
|
||||
v-model="customSource"
|
||||
type="text"
|
||||
placeholder="مثلاً اینستاگرام، سایت…"
|
||||
:disabled="disabled"
|
||||
@keydown.enter.prevent="addCustomSource"
|
||||
/>
|
||||
<UiButton
|
||||
v-if="!disabled"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
@click.prevent="addCustomSource"
|
||||
>
|
||||
افزودن
|
||||
</UiButton>
|
||||
</div>
|
||||
<label v-for="s in filter.sources ?? []" :key="s" class="chip">
|
||||
<input type="checkbox" checked @change="toggleInList('sources', s)" />
|
||||
{{ s }}
|
||||
</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="showNoteTypes" :disabled="disabled">
|
||||
<legend>نوع یادداشت</legend>
|
||||
<label v-for="t in noteTypeOptions" :key="t.value" class="chip">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="filter.sources?.includes(t.value)"
|
||||
@change="toggleInList('sources', t.value)"
|
||||
/>
|
||||
{{ t.label }}
|
||||
</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="showTicketStatuses" :disabled="disabled">
|
||||
<legend>وضعیت تیکت</legend>
|
||||
<label v-for="t in ticketStatusOptions" :key="t.value" class="chip">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="filter.sources?.includes(t.value)"
|
||||
@change="toggleInList('sources', t.value)"
|
||||
/>
|
||||
{{ t.label }}
|
||||
</label>
|
||||
</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="showMoods" :disabled="disabled">
|
||||
<legend>حال مشتری</legend>
|
||||
<label v-for="m in PERSON_MOODS" :key="m" class="chip">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="filter.moods?.includes(m)"
|
||||
@change="toggleInList('moods', m)"
|
||||
/>
|
||||
{{ PERSON_MOOD_LABELS[m] }}
|
||||
</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="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="filter.pipelineIds?.includes(p.id)"
|
||||
@change="toggleInList('pipelineIds', p.id)"
|
||||
/>
|
||||
{{ p.name }}
|
||||
</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="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="filter.stageIds?.includes(s.id)"
|
||||
@change="toggleInList('stageIds', s.id)"
|
||||
/>
|
||||
{{ s.name }}
|
||||
</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="showBoards" :disabled="disabled">
|
||||
<legend>{{ boardsLegend }}</legend>
|
||||
<p v-if="!boards.length" class="muted">بوردی نیست.</p>
|
||||
<label v-for="b in boards" :key="b.id" class="chip">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="filter.boardIds?.includes(b.id)"
|
||||
@change="toggleInList('boardIds', b.id)"
|
||||
/>
|
||||
{{ b.name }}
|
||||
</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>
|
||||
|
||||
<UiButton
|
||||
v-if="!disabled"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
@click.prevent="addRule"
|
||||
>
|
||||
+ قانون جدید
|
||||
</UiButton>
|
||||
<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="filter.typeIds?.includes(t.id)"
|
||||
@change="toggleInList('typeIds', t.id)"
|
||||
/>
|
||||
{{ t.name }}
|
||||
</label>
|
||||
</fieldset>
|
||||
</template>
|
||||
|
||||
<p v-else-if="!hasLimits" class="hint">
|
||||
بدون محدودیت عمقی — همهٔ {{ sectionLabel }} قابل مشاهدهاند.
|
||||
بدون محدودیت — همهٔ {{ sectionLabel }} قابل مشاهدهاند.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -508,25 +486,18 @@ function addCustomSource(index: number) {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.hint,
|
||||
.muted {
|
||||
.muted,
|
||||
.summary {
|
||||
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;
|
||||
.hint.warn {
|
||||
color: #b45309;
|
||||
}
|
||||
.dae-rule header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.summary {
|
||||
font-weight: 600;
|
||||
color: #334155;
|
||||
}
|
||||
fieldset {
|
||||
border: 1px dashed #e2e8f0;
|
||||
@@ -552,7 +523,7 @@ legend {
|
||||
border-radius: 8px;
|
||||
padding: 0.35rem 0.5rem;
|
||||
}
|
||||
.compact .dae-rule {
|
||||
padding: 0.55rem;
|
||||
.compact fieldset {
|
||||
padding: 0.45rem 0.55rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user