diff --git a/e2e/11-modules-catalog.spec.ts b/e2e/11-modules-catalog.spec.ts index 0538b05..e5bc7e3 100644 --- a/e2e/11-modules-catalog.spec.ts +++ b/e2e/11-modules-catalog.spec.ts @@ -158,4 +158,33 @@ test.describe('Scenario 11: Universal Modules Catalog & Profiles', () => { await catalogTab.click(); await expect(page.locator('[data-testid="modules-catalog-container"]')).toBeVisible(); }); + + test('8. Mobile filter drawer, density switcher, and quick access block function smoothly', async ({ page }) => { + // Quick access block + const quickAccess = page.locator('[data-testid="quick-access-block"]'); + await expect(quickAccess).toBeVisible(); + await expect(page.locator('[data-testid="quick-access-crm"]')).toBeVisible(); + + // Density toggle + const compactToggle = page.locator('[data-testid="density-toggle-compact"]'); + const detailedToggle = page.locator('[data-testid="density-toggle-detailed"]'); + await expect(compactToggle).toBeVisible(); + await expect(detailedToggle).toBeVisible(); + + await compactToggle.click(); + await expect(page.locator('text=Ключевые возможности:').first()).not.toBeVisible(); + + await detailedToggle.click(); + await expect(page.locator('text=Ключевые возможности:').first()).toBeVisible(); + + // Filter drawer + const filterDrawerBtn = page.locator('[data-testid="mobile-filter-drawer-btn"]'); + await expect(filterDrawerBtn).toBeVisible(); + await filterDrawerBtn.click(); + + const applyDrawerBtn = page.locator('[data-testid="apply-filters-drawer-btn"]'); + await expect(applyDrawerBtn).toBeVisible(); + await applyDrawerBtn.click(); + await expect(applyDrawerBtn).not.toBeVisible(); + }); }); diff --git a/src/features/catalog/CatalogFilterDrawer.tsx b/src/features/catalog/CatalogFilterDrawer.tsx new file mode 100644 index 0000000..23ea4ba --- /dev/null +++ b/src/features/catalog/CatalogFilterDrawer.tsx @@ -0,0 +1,184 @@ +import React from 'react'; +import { Drawer } from '../../shared/ui/Drawer'; +import { Button } from '../../shared/ui/Button'; +import { BUSINESS_PROFILES } from '../../core/profiles/profiles'; +import { CATALOG_CATEGORIES, READINESS_CONFIG, ReadinessStatus } from './catalogData'; +import { Building, Layers, ShieldCheck, RotateCcw, Check } from 'lucide-react'; + +export interface CatalogFilterDrawerProps { + isOpen: boolean; + onClose: () => void; + profileFilter: string; + onSelectProfileFilter: (profileId: string) => void; + categoryFilter: string; + onSelectCategoryFilter: (categoryId: string) => void; + statusFilter: string; + onSelectStatusFilter: (status: string) => void; + onReset: () => void; + resultsCount: number; +} + +export const CatalogFilterDrawer: React.FC = ({ + isOpen, + onClose, + profileFilter, + onSelectProfileFilter, + categoryFilter, + onSelectCategoryFilter, + statusFilter, + onSelectStatusFilter, + onReset, + resultsCount, +}) => { + return ( + + + + + + } + > +
+ {/* Section 1: Industry / Profile */} +
+
+ + Отраслевой фокус +
+ +
+ + + + + {Object.values(BUSINESS_PROFILES).map((p) => ( + + ))} +
+
+ + {/* Section 2: Categories */} +
+
+ + Категория +
+ +
+ {CATALOG_CATEGORIES.map((cat) => ( + + ))} +
+
+ + {/* Section 3: Status / Readiness */} +
+
+ + Статус реализации +
+ +
+ + + {(Object.keys(READINESS_CONFIG) as ReadinessStatus[]).map((st) => { + const info = READINESS_CONFIG[st]; + return ( + + ); + })} +
+
+
+
+ ); +}; diff --git a/src/features/catalog/ModuleCard.tsx b/src/features/catalog/ModuleCard.tsx index 0dee831..c4eac16 100644 --- a/src/features/catalog/ModuleCard.tsx +++ b/src/features/catalog/ModuleCard.tsx @@ -51,12 +51,14 @@ export interface ModuleCardProps { module: CatalogModuleItem; onOpenDetails: (module: CatalogModuleItem) => void; isRecommended?: boolean; + isCompact?: boolean; } export const ModuleCard: React.FC = ({ module, onOpenDetails, isRecommended = false, + isCompact = false, }) => { const navigate = useNavigate(); const readiness = READINESS_CONFIG[module.readiness]; @@ -69,17 +71,19 @@ export const ModuleCard: React.FC = ({ return (
-
+
{/* Top meta bar: category + badges */}
-
+
{ICON_COMPONENTS[module.iconName] || }
{isRecommended && ( @@ -91,38 +95,42 @@ export const ModuleCard: React.FC = ({
- - {availability.label} - + {!isCompact && ( + + {availability.label} + + )}
{/* Title and description */}
-

+

{module.title}

-

+

{module.description}

- {/* Features list */} -
- - Ключевые возможности: - - {module.features.slice(0, 3).map((f, i) => ( -
- - {f} -
- ))} -
+ {/* Features list (Only in detailed mode) */} + {!isCompact && ( +
+ + Ключевые возможности: + + {module.features.slice(0, 3).map((f, i) => ( +
+ + {f} +
+ ))} +
+ )}
{/* Action buttons */} -
+
{/* Stats badges */} @@ -214,6 +243,97 @@ export const ModulesCatalog: React.FC = () => {
+ {/* Quick Controls Bar: Mobile Filter Button + Density Toggle */} +
+ {/* Filter Drawer Trigger Button */} + + + {/* Density Toggle */} +
+ + +
+
+ + {/* Quick Access Block */} +
+
+
+ + Быстрый доступ +
+ Ключевые модули платформы +
+
+ {[ + { id: 'crm', title: 'CRM', path: '/crm', icon: Users, color: 'text-sky-500' }, + { id: 'pos', title: 'Продажи', path: '/sales', icon: ShoppingBag, color: 'text-emerald-500' }, + { id: 'operations', title: 'Касса и операции', path: '/operations', icon: Store, color: 'text-amber-500' }, + { id: 'inventory', title: 'Склад', path: '/inventory', icon: Package, color: 'text-blue-500' }, + { id: 'finance', title: 'Финансы', path: '/finance', icon: CreditCard, color: 'text-purple-500' }, + { id: 'tasks', title: 'Задачи', path: '/tasks', icon: CheckSquare, color: 'text-rose-500' }, + ].map((item) => { + const Icon = item.icon; + return ( + + ); + })} +
+
+ {/* Industry Profiles Filter Bar */}
@@ -393,6 +513,7 @@ export const ModulesCatalog: React.FC = () => { key={mod.id} module={mod} isRecommended={true} + isCompact={displayDensity === 'compact'} onOpenDetails={setSelectedModuleForModal} /> ))} @@ -425,6 +546,7 @@ export const ModulesCatalog: React.FC = () => { key={mod.id} module={mod} isRecommended={false} + isCompact={displayDensity === 'compact'} onOpenDetails={setSelectedModuleForModal} /> ))} @@ -457,6 +579,7 @@ export const ModulesCatalog: React.FC = () => { ))} @@ -474,6 +597,22 @@ export const ModulesCatalog: React.FC = () => {
)} + {/* Mobile Bottom-Sheet Filter Drawer */} + setIsFilterDrawerOpen(false)} + profileFilter={catalogProfileFilter} + onSelectProfileFilter={(id) => setCatalogProfileFilter(id)} + categoryFilter={catalogCategoryFilter} + onSelectCategoryFilter={(id) => setCatalogCategoryFilter(id as any)} + statusFilter={catalogStatusFilter} + onSelectStatusFilter={(id) => setCatalogStatusFilter(id)} + onReset={() => { + resetCatalogFilters(); + }} + resultsCount={isProfileFilterActive ? recommendedList.length + otherList.length : filteredModules.length} + /> + {/* Module Info Modal */}