feat: update permissions

This commit is contained in:
ivannoskov
2026-02-10 17:41:11 +03:00
parent e1117e46c8
commit f0574db79f
7 changed files with 335 additions and 140 deletions

View File

@@ -6,7 +6,7 @@
import { useEffect, useState, useMemo } from "react";
import { useParams } from "next/navigation";
import { Loader2, TrendingUp, BarChart3, LineChart as LineChartIcon, Info } from "lucide-react";
import { Loader2, TrendingUp, BarChart3, LineChart as LineChartIcon, Info, Lock } from "lucide-react";
import {
LineChart,
Line,
@@ -132,17 +132,47 @@ const chartConfig: ChartConfig = {
export default function SpendingAnalyticsPage() {
const params = useParams();
const workspaceId = params?.workspaceId as string;
const { selectedProjects, getProjectFilterId, allProjectsSelected } = useWorkspace();
const { selectedProjects, getProjectFilterId, allProjectsSelected, canViewSubscriptions, hasAnyAnalyticsPermission } = useWorkspace();
const projectFilterId = getProjectFilterId();
const canViewSubs = canViewSubscriptions;
const hasPermission = hasAnyAnalyticsPermission;
const [spending, setSpending] = useState<SpendingAnalytics | null>(null);
const [loading, setLoading] = useState(true);
const [grouping, setGrouping] = useState<DateGrouping>("month");
if (!hasPermission) {
return (
<TooltipProvider>
<DashboardHeader
breadcrumbs={[
{ label: "Главная", href: `/dashboard/${workspaceId}` },
{ label: "Аналитика", href: `/dashboard/${workspaceId}/analytics` },
{ label: "Расходы" },
]}
/>
<div className="flex flex-1 flex-col gap-4 p-4 pt-0 mt-4">
<Card>
<CardContent className="flex flex-col items-center justify-center py-16">
<Lock className="h-12 w-12 text-muted-foreground mb-4" />
<h3 className="text-lg font-semibold mb-2">Доступ ограничен</h3>
<p className="text-muted-foreground text-center">
У вас нет разрешения на просмотр аналитики. Обратитесь к администратору рабочего пространства.
</p>
</CardContent>
</Card>
</div>
</TooltipProvider>
);
}
// Chart settings
const [chartType, setChartType] = useState<ChartType>("line");
const [showLabels, setShowLabels] = useState(false);
const [selectedMetrics, setSelectedMetrics] = useState<MetricKey[]>(["cost", "subscriptions"]);
const [selectedMetrics, setSelectedMetrics] = useState<MetricKey[]>(
canViewSubs ? ["cost", "subscriptions"] : ["cost"]
);
useEffect(() => {
loadData();
@@ -320,16 +350,18 @@ export default function SpendingAnalyticsPage() {
<div className="text-2xl font-bold">{formatCurrency(spending.total_cost)}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium">Подписчиков</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{formatNumber(spending.total_subscriptions)}
</div>
</CardContent>
</Card>
{canViewSubs && (
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium">Подписчиков</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{formatNumber(spending.total_subscriptions)}
</div>
</CardContent>
</Card>
)}
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium">Просмотров</CardTitle>
@@ -395,29 +427,48 @@ export default function SpendingAnalyticsPage() {
</div>
</div>
{/* Metrics Selection */}
<div className="flex items-center gap-4 mt-3 pt-3 border-t">
<span className="text-sm text-muted-foreground">Метрики:</span>
{(["cost", "subscriptions", "views"] as MetricKey[]).map((metric) => (
<div key={metric} className="flex items-center gap-2">
<Checkbox
id={`metric-${metric}`}
checked={selectedMetrics.includes(metric)}
onCheckedChange={() => handleToggleMetric(metric)}
/>
<Label
htmlFor={`metric-${metric}`}
className="text-sm cursor-pointer flex items-center gap-1.5"
>
<div
className="h-2.5 w-2.5 rounded-full"
style={{ backgroundColor: `var(--color-${metric})` }}
{/* Metrics Selection */}
<div className="flex items-center gap-4 mt-3 pt-3 border-t">
<span className="text-sm text-muted-foreground">Метрики:</span>
{(["cost", "views"] as MetricKey[]).map((metric) => (
<div key={metric} className="flex items-center gap-2">
<Checkbox
id={`metric-${metric}`}
checked={selectedMetrics.includes(metric)}
onCheckedChange={() => handleToggleMetric(metric)}
/>
{chartConfig[metric].label}
</Label>
</div>
))}
</div>
<Label
htmlFor={`metric-${metric}`}
className="text-sm cursor-pointer flex items-center gap-1.5"
>
<div
className="h-2.5 w-2.5 rounded-full"
style={{ backgroundColor: `var(--color-${metric})` }}
/>
{chartConfig[metric].label}
</Label>
</div>
))}
{canViewSubs && (["subscriptions"] as MetricKey[]).map((metric) => (
<div key={metric} className="flex items-center gap-2">
<Checkbox
id={`metric-${metric}`}
checked={selectedMetrics.includes(metric)}
onCheckedChange={() => handleToggleMetric(metric)}
/>
<Label
htmlFor={`metric-${metric}`}
className="text-sm cursor-pointer flex items-center gap-1.5"
>
<div
className="h-2.5 w-2.5 rounded-full"
style={{ backgroundColor: `var(--color-${metric})` }}
/>
{chartConfig[metric].label}
</Label>
</div>
))}
</div>
</CardHeader>
<CardContent className="px-2 sm:p-6">{renderChart()}</CardContent>
</Card>
@@ -435,19 +486,21 @@ export default function SpendingAnalyticsPage() {
<TableRow>
<TableHead>Период</TableHead>
<TableHead className="text-right">Расходы</TableHead>
<TableHead className="text-right">Подписчики</TableHead>
{canViewSubs && <TableHead className="text-right">Подписчики</TableHead>}
<TableHead className="text-right">Просмотры</TableHead>
<TableHead className="text-right">
<div className="flex items-center justify-end gap-1">
CPS
<Tooltip>
<TooltipTrigger asChild>
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>Cost Per Subscription</TooltipContent>
</Tooltip>
</div>
</TableHead>
{canViewSubs && (
<TableHead className="text-right">
<div className="flex items-center justify-end gap-1">
CPS
<Tooltip>
<TooltipTrigger asChild>
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>Cost Per Subscription</TooltipContent>
</Tooltip>
</div>
</TableHead>
)}
<TableHead className="text-right">
<div className="flex items-center justify-end gap-1">
CPM
@@ -470,13 +523,17 @@ export default function SpendingAnalyticsPage() {
<TableRow key={index}>
<TableCell className="font-medium">{item.period}</TableCell>
<TableCell className="text-right">{formatCurrency(item.cost)}</TableCell>
<TableCell className="text-right">
{formatNumber(item.subscriptions)}
</TableCell>
{canViewSubs && (
<TableCell className="text-right">
{formatNumber(item.subscriptions)}
</TableCell>
)}
<TableCell className="text-right">{formatNumber(item.views)}</TableCell>
<TableCell className="text-right">
{cps ? formatCurrency(cps) : "—"}
</TableCell>
{canViewSubs && (
<TableCell className="text-right">
{cps ? formatCurrency(cps) : "—"}
</TableCell>
)}
<TableCell className="text-right">
{cpm ? formatCurrency(cpm) : "—"}
</TableCell>