diff --git a/apps/web/src/components/PhoneContactsImport.vue b/apps/web/src/components/PhoneContactsImport.vue index 199ca05..a77569c 100644 --- a/apps/web/src/components/PhoneContactsImport.vue +++ b/apps/web/src/components/PhoneContactsImport.vue @@ -7,9 +7,11 @@ import { toFaDigits } from '@/lib/jalali'; import { UiButton, UiModal } from '@/components/ui'; import UiIcon from '@/components/UiIcon.vue'; import { + filterPhoneContacts, isContactPickerSupported, parseVCardFile, pickPhoneContacts, + sortPhoneContacts, type PhoneContactDraft, } from '@/lib/phone-contacts'; @@ -30,18 +32,27 @@ const phase = ref<'closed' | 'help' | 'review'>('closed'); const importing = ref(false); const drafts = ref([]); const selected = ref>(new Set()); +const listQuery = ref(''); const fileInput = ref(null); const modalOpen = computed(() => phase.value !== 'closed'); const modalTitle = computed(() => phase.value === 'review' ? 'انتخاب مخاطبین برای ورود' : 'ورود از مخاطبین گوشی', ); +const visibleDrafts = computed(() => + filterPhoneContacts(drafts.value, listQuery.value), +); const selectedCount = computed( () => drafts.value.filter((d) => selected.value.has(d.key)).length, ); const allSelected = computed( () => drafts.value.length > 0 && selectedCount.value === drafts.value.length, ); +const visibleAllSelected = computed( + () => + visibleDrafts.value.length > 0 && + visibleDrafts.value.every((d) => selected.value.has(d.key)), +); function apiMessage(e: unknown, fallback: string) { if (axios.isAxiosError(e)) { @@ -52,8 +63,9 @@ function apiMessage(e: unknown, fallback: string) { } async function showReview(rows: PhoneContactDraft[], opts?: { selectAll?: boolean }) { - drafts.value = rows; - selected.value = opts?.selectAll ? new Set(rows.map((r) => r.key)) : new Set(); + drafts.value = sortPhoneContacts(rows); + listQuery.value = ''; + selected.value = opts?.selectAll ? new Set(drafts.value.map((r) => r.key)) : new Set(); // بعد از دیالوگ فایل، یک تیک صبر کن تا مودال مطمئن باز شود phase.value = 'closed'; await nextTick(); @@ -68,13 +80,25 @@ function toggle(key: string) { } function selectAll(on: boolean) { - selected.value = on ? new Set(drafts.value.map((d) => d.key)) : new Set(); + if (!on) { + selected.value = new Set(); + return; + } + // اگر جستجو فعال است فقط نتایج را انتخاب کن، وگرنه همه + if (listQuery.value.trim()) { + const next = new Set(selected.value); + for (const row of visibleDrafts.value) next.add(row.key); + selected.value = next; + } else { + selected.value = new Set(drafts.value.map((d) => d.key)); + } } function closeAll() { phase.value = 'closed'; drafts.value = []; selected.value = new Set(); + listQuery.value = ''; importing.value = false; } @@ -98,7 +122,6 @@ async function startPicker() { emit('fail', 'مخاطبی انتخاب نشد.'); return; } - // از Contact Picker قبلاً انتخاب شده‌اند → همه تیک‌خورده، ولی هنوز قابل کم/زیاد کردن await showReview(rows, { selectAll: true }); } catch (e) { const name = e instanceof DOMException ? e.name : ''; @@ -124,7 +147,6 @@ async function onVcfFile(ev: Event) { phase.value = 'help'; return; } - // از فایل: هیچ‌کدام پیش‌فرض انتخاب نشود تا کاربر خودش یک/چند/همه را بزند await showReview(rows, { selectAll: false }); } catch { emit('fail', 'خواندن فایل مخاطبین (.vcf) ناموفق بود.'); @@ -205,16 +227,35 @@ async function importSelected() {