Add searchable assignee and creator filters on the routines manage toolbar.

EOF

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-14 20:45:37 +03:30
co-authored by Cursor
parent fb6aa77465
commit d282627af7
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, inject, onMounted, reactive, ref, watch } from 'vue'; import { computed, inject, onMounted, reactive, ref, watch } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { Select as ASelect } from 'ant-design-vue';
import type { RoutineCategoryDto, RoutineDto } from '@mento/shared'; import type { RoutineCategoryDto, RoutineDto } from '@mento/shared';
import UiIcon from '@/components/UiIcon.vue'; import UiIcon from '@/components/UiIcon.vue';
import UserAvatar from '@/components/UserAvatar.vue'; import UserAvatar from '@/components/UserAvatar.vue';
@@ -28,7 +29,8 @@ const loading = ref(true);
const error = ref(''); const error = ref('');
const q = ref(''); const q = ref('');
const status = ref<'all' | 'active' | 'inactive'>('all'); const status = ref<'all' | 'active' | 'inactive'>('all');
const assignee = ref(''); const assignee = ref<string | undefined>(undefined);
const creator = ref<string | undefined>(undefined);
const busyId = ref<string | null>(null); const busyId = ref<string | null>(null);
const menuFor = ref<string | null>(null); const menuFor = ref<string | null>(null);
@@ -60,6 +62,7 @@ const filtered = computed(() => {
if (status.value === 'active' && !r.isActive) return false; if (status.value === 'active' && !r.isActive) return false;
if (status.value === 'inactive' && r.isActive) return false; if (status.value === 'inactive' && r.isActive) return false;
if (assignee.value && r.assignee?.id !== assignee.value && `team:${r.team?.id}` !== assignee.value) return false; if (assignee.value && r.assignee?.id !== assignee.value && `team:${r.team?.id}` !== assignee.value) return false;
if (creator.value && (r.createdBy?.id ?? r.createdById) !== creator.value) return false;
if (term && !r.title.includes(term)) return false; if (term && !r.title.includes(term)) return false;
return true; return true;
}); });
@@ -208,21 +211,53 @@ async function deleteCat(c: RoutineCategoryDto) {
const users = computed(() => shell.context.value?.users ?? []); const users = computed(() => shell.context.value?.users ?? []);
const teams = computed(() => shell.context.value?.teams ?? []); const teams = computed(() => shell.context.value?.teams ?? []);
const assigneeFilterOptions = computed(() => [
...users.value.map((u) => ({ value: u.id, label: u.fullName })),
...teams.value.map((t) => ({ value: `team:${t.id}`, label: `تیم ${t.name}` })),
]);
const creatorFilterOptions = computed(() =>
users.value.map((u) => ({ value: u.id, label: u.fullName })),
);
function filterSelectOption(input: string, option?: { label?: string }) {
const q = input.trim().toLocaleLowerCase('fa');
if (!q) return true;
return String(option?.label ?? '')
.toLocaleLowerCase('fa')
.includes(q);
}
</script> </script>
<template> <template>
<div class="manage"> <div class="manage">
<div class="toolbar"> <div class="toolbar">
<UiSearch v-model="q" placeholder="جستجوی عنوان…" class="search" /> <UiSearch v-model="q" placeholder="جستجوی عنوان…" class="search" />
<UiSelect v-model="assignee" size="sm" class="who"> <div class="who">
<option value="">همه مسئول‌ها</option> <ASelect
<optgroup label="کاربران"> v-model:value="assignee"
<option v-for="u in users" :key="u.id" :value="u.id">{{ u.fullName }}</option> allow-clear
</optgroup> show-search
<optgroup v-if="teams.length" label="تیم‌ها"> placeholder="همه مسئول‌ها"
<option v-for="t in teams" :key="t.id" :value="`team:${t.id}`">{{ t.name }}</option> :options="assigneeFilterOptions"
</optgroup> :filter-option="filterSelectOption"
</UiSelect> :get-popup-container="(node) => node.parentElement ?? document.body"
style="width: 100%"
/>
</div>
<div class="who">
<ASelect
v-model:value="creator"
allow-clear
show-search
placeholder="همه سازنده‌ها"
:options="creatorFilterOptions"
:filter-option="filterSelectOption"
:get-popup-container="(node) => node.parentElement ?? document.body"
style="width: 100%"
/>
</div>
<UiSegmented v-model="status" :options="statusOptions" /> <UiSegmented v-model="status" :options="statusOptions" />
<span class="spacer" /> <span class="spacer" />
<UiButton variant="ghost" size="sm" @click="catsOpen = true; editCat(null)"> <UiButton variant="ghost" size="sm" @click="catsOpen = true; editCat(null)">