From 233bdf79075f77f802d74024705df46e06f610f4 Mon Sep 17 00:00:00 2001 From: ivannoskov Date: Mon, 2 Feb 2026 21:29:06 +0300 Subject: [PATCH] feat: settings page update --- .../purchase-plans/[projectId]/page.tsx | 2 +- app/dashboard/[workspaceId]/settings/page.tsx | 23 +-- components/filters-modal.tsx | 160 +++++++++--------- components/layout/app-sidebar.tsx | 6 +- components/providers/workspace-provider.tsx | 27 ++- lib/types/api.ts | 34 +++- 6 files changed, 140 insertions(+), 112 deletions(-) diff --git a/app/dashboard/[workspaceId]/purchase-plans/[projectId]/page.tsx b/app/dashboard/[workspaceId]/purchase-plans/[projectId]/page.tsx index e0353b1..f5048a0 100644 --- a/app/dashboard/[workspaceId]/purchase-plans/[projectId]/page.tsx +++ b/app/dashboard/[workspaceId]/purchase-plans/[projectId]/page.tsx @@ -1699,7 +1699,7 @@ export default function PurchasePlanDetailPage() { onClick={() => setFiltersOpen(true)} > - Фильтры + Фильтры и сортировка {hasActiveFilters && ( ✓ diff --git a/app/dashboard/[workspaceId]/settings/page.tsx b/app/dashboard/[workspaceId]/settings/page.tsx index cf2a5a0..6c9e564 100644 --- a/app/dashboard/[workspaceId]/settings/page.tsx +++ b/app/dashboard/[workspaceId]/settings/page.tsx @@ -59,28 +59,7 @@ import { AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import type { WorkspaceMember, WorkspaceInvite, PermissionKey } from "@/lib/types/api"; - -// ============================================================================ -// Constants -// ============================================================================ - -const PERMISSION_LABELS: Record = { - admin_full: "Полный доступ", - projects_read: "Просмотр проектов", - projects_write: "Редактирование проектов", - placements_read: "Просмотр размещений", - placements_write: "Редактирование размещений", - analytics_read: "Просмотр аналитики", -}; - -const ALL_PERMISSIONS: PermissionKey[] = [ - "admin_full", - "projects_read", - "projects_write", - "placements_read", - "placements_write", - "analytics_read", -]; +import { PERMISSION_LABELS, ALL_PERMISSIONS } from "@/lib/types/api"; // ============================================================================ // Component diff --git a/components/filters-modal.tsx b/components/filters-modal.tsx index 4499661..23e0d1d 100644 --- a/components/filters-modal.tsx +++ b/components/filters-modal.tsx @@ -226,10 +226,10 @@ export function FiltersModal({ - Фильтры + Фильтры и сортировка - Настройте фильтры для каналов и размещений + Настройте фильтры и сортировку для каналов и размещений @@ -254,7 +254,6 @@ export function FiltersModal({ : "border-transparent text-muted-foreground hover:text-foreground" )} > - Размещения @@ -553,84 +552,87 @@ export function FiltersModal({ -
- -
- - {filters.sort && ( -
- - -
- )} - {filters.sort && ( - + +
+ )} + {filters.sort && ( + + )} +
diff --git a/components/layout/app-sidebar.tsx b/components/layout/app-sidebar.tsx index 00b431b..b016958 100644 --- a/components/layout/app-sidebar.tsx +++ b/components/layout/app-sidebar.tsx @@ -100,7 +100,7 @@ interface NavItem { url: string; icon?: LucideIcon; isActive?: boolean; - requiredPermission?: "projects_read" | "placements_read" | "analytics_read" | "admin_full"; + requiredPermission?: "admin_full" | "projects_read" | "projects_write" | "creatives_read" | "creatives_write" | "placements_read" | "placements_write" | "analytics_read" | "analytics_without_clicks" | "analytics_own_creatives"; tooltip?: string; items?: { title: string; @@ -404,8 +404,8 @@ export function AppSidebar({ ...props }: React.ComponentProps) { }); } - // 5. Аналитика — только подразделы - if (hasPermission("analytics_read")) { + // 5. Аналитика — проверяем любой из аналитических доступов + if (hasPermission("analytics_read") || hasPermission("analytics_without_clicks") || hasPermission("analytics_own_creatives")) { items.push({ title: "Аналитика", url: buildRoute.analytics(workspaceId), diff --git a/components/providers/workspace-provider.tsx b/components/providers/workspace-provider.tsx index 84bb1b7..12a11ba 100644 --- a/components/providers/workspace-provider.tsx +++ b/components/providers/workspace-provider.tsx @@ -53,7 +53,7 @@ interface WorkspaceContextType { // Permissions permissions: Permission[]; - hasPermission: (key: PermissionKey, projectId?: string) => boolean; + hasPermission: (key: PermissionKey, projectId?: string, creativeId?: string, placementId?: string) => boolean; isAdmin: boolean; // Check if user is admin in a specific workspace isWorkspaceAdmin: (workspaceId: string) => Promise; @@ -141,7 +141,7 @@ export const WorkspaceProvider: React.FC = ({ const isAdmin = permissions.some((p) => p.key === "admin_full"); const hasPermission = useCallback( - (key: PermissionKey, projectId?: string): boolean => { + (key: PermissionKey, projectId?: string, creativeId?: string, placementId?: string): boolean => { // Admin has all permissions if (isAdmin) return true; @@ -154,13 +154,30 @@ export const WorkspaceProvider: React.FC = ({ // If projectId provided, check if it's in scopes if (projectId) { - return permission.scopes.some( + const hasProjectScope = permission.scopes.some( (s) => s.type === "project" && s.id === projectId ); + if (hasProjectScope) return true; } - // Permission exists but is scoped - need projectId to verify - return true; + // If creativeId provided, check if it's in scopes + if (creativeId) { + const hasCreativeScope = permission.scopes.some( + (s) => s.type === "creative" && s.id === creativeId + ); + if (hasCreativeScope) return true; + } + + // If placementId provided, check if it's in scopes + if (placementId) { + const hasPlacementScope = permission.scopes.some( + (s) => s.type === "placement" && s.id === placementId + ); + if (hasPlacementScope) return true; + } + + // Permission exists but is scoped and no matching scope found + return false; }, [permissions, isAdmin] ); diff --git a/lib/types/api.ts b/lib/types/api.ts index 623b0b4..12f1cbc 100644 --- a/lib/types/api.ts +++ b/lib/types/api.ts @@ -64,11 +64,15 @@ export type PermissionKey = | "admin_full" | "projects_read" | "projects_write" + | "creatives_read" + | "creatives_write" | "placements_read" | "placements_write" - | "analytics_read"; + | "analytics_read" + | "analytics_without_clicks" + | "analytics_own_creatives"; -export type PermissionScopeType = "project"; +export type PermissionScopeType = "project" | "creative" | "placement"; export interface PermissionScope { type: PermissionScopeType; @@ -80,6 +84,32 @@ export interface Permission { scopes?: PermissionScope[]; } +export const PERMISSION_LABELS: Record = { + admin_full: "Полный доступ", + projects_read: "Просмотр проектов", + projects_write: "Редактирование проектов", + creatives_read: "Просмотр креативов", + creatives_write: "Создание/редактирование креативов", + placements_read: "Просмотр планов закупок", + placements_write: "Создание/редактирование закупок", + analytics_read: "Полный доступ к статистике", + analytics_without_clicks: "Статистика без переходов", + analytics_own_creatives: "Статистика только своих креативов", +}; + +export const ALL_PERMISSIONS: PermissionKey[] = [ + "admin_full", + "projects_read", + "projects_write", + "creatives_read", + "creatives_write", + "placements_read", + "placements_write", + "analytics_read", + "analytics_without_clicks", + "analytics_own_creatives", +]; + export interface WorkspaceMemberUser { id: string; // user_id telegram_id: number;