diff --git a/app/resources/js/views/DealsView.vue b/app/resources/js/views/DealsView.vue index 7f00b6c1..5d26d307 100644 --- a/app/resources/js/views/DealsView.vue +++ b/app/resources/js/views/DealsView.vue @@ -37,11 +37,41 @@ import { buildCsvString, triggerBlobDownload, triggerCsvDownload } from '../comp // Task 15: density-toggle composable (persists в localStorage, влияет на row height). const { rowHeight } = useDensity(); -// Task 15: stub-обработчики redesign-filter-chip'ов. На I1 — popover'ы Проект/Менеджер -// не реализованы; chiprow служит quiet-luxury визуальной заменой для status-summary'ов. -// Не ломает существующие VSelect'ы в DealsFilters — те остаются как полноценный filter UI. +// Sprint 1 C2: popovers для Проект/Менеджер chip'ов. Draft-state накапливает +// выбор в v-menu, при «Применить» переносится в filterProjects/filterManagers. +// Status chip остаётся read-only (P2 backlog в Sprint 5). +const projectMenuOpen = ref(false); +const managerMenuOpen = ref(false); +const projectMenuDraft = ref([]); +const managerMenuDraft = ref([]); + function onRedesignFilterClick(name: string): void { - console.log(`[redesign filterbar] ${name} clicked — popover TBD`); + if (name === 'Проект') { + projectMenuDraft.value = [...filterProjects.value]; + projectMenuOpen.value = true; + } else if (name === 'Менеджер') { + managerMenuDraft.value = [...filterManagers.value]; + managerMenuOpen.value = true; + } + // 'Статус' — read-only summary, popover откроется в Sprint 5 (P2). +} + +function applyProjectFilter(): void { + filterProjects.value = [...projectMenuDraft.value]; + projectMenuOpen.value = false; +} + +function applyManagerFilter(): void { + filterManagers.value = [...managerMenuDraft.value]; + managerMenuOpen.value = false; +} + +function clearProjectDraft(): void { + projectMenuDraft.value = []; +} + +function clearManagerDraft(): void { + managerMenuDraft.value = []; } const auth = useAuthStore(); @@ -350,6 +380,13 @@ defineExpose({ trashMode, toggleTrashMode, applyBulkRestoreFromTrash, + projectMenuOpen, + managerMenuOpen, + projectMenuDraft, + managerMenuDraft, + applyProjectFilter, + applyManagerFilter, + onRedesignFilterClick, }); const leadStatuses = computed(() => leadStatusesStore.statuses); @@ -463,10 +500,9 @@ const waitingPay = computed(() => dealsState.filter((d) => d.statusSlug === 'wai @clear-filters="clearFilters" /> - +
dealsState.filter((d) => d.statusSlug === 'wai :active="false" @click="onRedesignFilterClick('Статус')" /> - - + + + + + + + Нет проектов в текущем списке + + + + {{ proj }} + + + + + + Очистить + + + + Применить + + + + + + + + + + + Нет менеджеров в текущем списке + + + + {{ mgr.name }} + {{ mgr.initials }} + + + + + + Очистить + + + + Применить + + + +
diff --git a/app/tests/Frontend/DealsViewRedesign.spec.ts b/app/tests/Frontend/DealsViewRedesign.spec.ts index c9e13fd1..e76cb2df 100644 --- a/app/tests/Frontend/DealsViewRedesign.spec.ts +++ b/app/tests/Frontend/DealsViewRedesign.spec.ts @@ -5,6 +5,7 @@ import { createVuetify } from 'vuetify'; import { createMemoryHistory, createRouter } from 'vue-router'; import DealsView from '../../resources/js/views/DealsView.vue'; + function setup() { setActivePinia(createPinia()); const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/deals', component: DealsView }] }); @@ -53,3 +54,55 @@ describe('DealsView — redesigned', () => { expect(w.html()).toMatch(/ld-stagger-row/); }); }); + +describe('FilterChip popovers (Sprint 1 C2)', () => { + it('clicking Project chip toggles projectMenuOpen ref to true', async () => { + const wrapper = mount(DealsView, { + global: { + plugins: [createPinia(), createVuetify()], + stubs: { DealDetailDrawer: true, NewDealDialog: true, VMenu: { template: '
' } }, + }, + }); + await new Promise((r) => setTimeout(r, 50)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const vm = wrapper.vm as any; + expect(vm.projectMenuOpen).toBe(false); + // Trigger chip click via exposed handler + vm.onRedesignFilterClick('Проект'); + await wrapper.vm.$nextTick(); + expect(vm.projectMenuOpen).toBe(true); + }); + + it('clicking Manager chip toggles managerMenuOpen ref to true', async () => { + const wrapper = mount(DealsView, { + global: { + plugins: [createPinia(), createVuetify()], + stubs: { DealDetailDrawer: true, NewDealDialog: true, VMenu: { template: '
' } }, + }, + }); + await new Promise((r) => setTimeout(r, 50)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const vm = wrapper.vm as any; + expect(vm.managerMenuOpen).toBe(false); + vm.onRedesignFilterClick('Менеджер'); + await wrapper.vm.$nextTick(); + expect(vm.managerMenuOpen).toBe(true); + }); + + it('applying project selection updates filterProjects and closes menu', async () => { + const wrapper = mount(DealsView, { + global: { + plugins: [createPinia(), createVuetify()], + stubs: { DealDetailDrawer: true, NewDealDialog: true, VMenu: { template: '
' } }, + }, + }); + await new Promise((r) => setTimeout(r, 50)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const vm = wrapper.vm as any; + vm.projectMenuDraft = ['demo-project-1', 'demo-project-2']; + vm.applyProjectFilter(); + await wrapper.vm.$nextTick(); + expect(vm.filterProjects).toEqual(['demo-project-1', 'demo-project-2']); + expect(vm.projectMenuOpen).toBe(false); + }); +});