feat: update permissions

This commit is contained in:
ivannoskov
2026-02-10 17:15:38 +03:00
parent 6941915d76
commit e1117e46c8
13 changed files with 961 additions and 523 deletions

View File

@@ -83,6 +83,7 @@ import { PlacementStatusSelector } from "@/components/placement-status-selector"
import { PlacementPostStatusSelector } from "@/components/placement-post-status-selector";
import { PlacementWizard } from "@/components/placement-wizard";
import { cn } from "@/lib/utils";
import { toast } from "sonner";
import type {
Project,
Placement,
@@ -742,9 +743,12 @@ function CellRenderer({
);
case "subscriptions":
const subsCountValue = placement.placement_post?.subscriptions_count;
// Show "—" if subscriptions are hidden due to permissions (0 means hidden)
const showSubs = subsCountValue === 0 ? "—" : formatNumber(subsCountValue);
return (
<td key={column.id} className="py-2 px-3 text-muted-foreground text-xs">
{formatNumber(placement.placement_post?.subscriptions_count)}
{showSubs}
</td>
);
@@ -1076,7 +1080,7 @@ export default function PurchasePlanDetailPage() {
const searchParams = useSearchParams();
const workspaceId = params?.workspaceId as string;
const projectId = params?.projectId as string;
const { projects, hasPermission } = useWorkspace();
const { projects, hasPermission, canViewAnalytics, canViewSubscriptions } = useWorkspace();
const [placements, setPlacements] = useState<Placement[]>([]);
const [loading, setLoading] = useState(true);
@@ -1098,9 +1102,30 @@ export default function PurchasePlanDetailPage() {
const [showCopyDialog, setShowCopyDialog] = useState(false);
const [targetProjectId, setTargetProjectId] = useState<string | null>(null);
const [copyingChannels, setCopyingChannels] = useState(false);
const [visibleColumns, setVisibleColumns] = useState<Set<string>>(
new Set(DEFAULT_VISIBLE_COLUMNS)
);
// Initialize visible columns based on permissions
const initialVisibleColumns = useMemo(() => {
const columns = new Set(DEFAULT_VISIBLE_COLUMNS);
// Hide subscription-related columns if user doesn't have analytics_read permission
if (!canViewSubscriptions) {
columns.delete("subscriptions");
columns.delete("cpf");
columns.delete("conversion_24h");
columns.delete("conversion_48h");
columns.delete("conversion_total");
columns.delete("total_unsubs");
columns.delete("unsub_percent");
columns.delete("total_active");
}
// Hide views and cpm if user has no analytics permissions at all
if (!canViewAnalytics) {
columns.delete("views");
columns.delete("cpm");
}
return columns;
}, [canViewSubscriptions, canViewAnalytics]);
const [visibleColumns, setVisibleColumns] = useState<Set<string>>(initialVisibleColumns);
const [showColumnsDialog, setShowColumnsDialog] = useState(false);
// Creative selection dialog
@@ -1228,14 +1253,15 @@ export default function PurchasePlanDetailPage() {
const handleOpenCreativeSend = async (placement: PlacementWithStats) => {
if (!placement.creative_id || isDemoMode) return;
try {
await placementsApi.sendCreative(workspaceId, projectId, placement.id);
alert("Креатив успешно отправлен");
} catch (err) {
console.error("Failed to send creative:", err);
alert("Не удалось отправить креатив");
}
await toast.promise(
placementsApi.sendCreative(workspaceId, projectId, placement.id),
{
loading: "Отправка креатива...",
success: "Креатив успешно отправлен",
error: "Не удалось отправить креатив",
}
);
};
const handleSelectCreative = (creative: { id: string; name: string }) => {

View File

@@ -69,7 +69,7 @@ export default function PurchasePlansPage() {
const params = useParams();
const router = useRouter();
const workspaceId = params?.workspaceId as string;
const { projects, isLoadingProjects, currentWorkspace } = useWorkspace();
const { projects, isLoadingProjects, currentWorkspace, canViewAnalytics, canViewSubscriptions } = useWorkspace();
const [placementsByProject, setPlacementsByProject] = useState<Record<string, Placement[]>>({});
const [loadingStats, setLoadingStats] = useState(true);
@@ -222,13 +222,17 @@ export default function PurchasePlansPage() {
<div className="flex items-center gap-2">
<Eye className="h-4 w-4 text-muted-foreground" />
<span className="text-sm text-muted-foreground">Охват:</span>
<span className="font-medium">{formatCompact(summary.total_views)}</span>
<span className="font-medium">
{canViewAnalytics ? formatCompact(summary.total_views) : "—"}
</span>
</div>
<div className="flex items-center gap-2">
<Users className="h-4 w-4 text-muted-foreground" />
<span className="text-sm text-muted-foreground">Подписок:</span>
<span className="font-medium">{formatCompact(summary.total_subscriptions)}</span>
<span className="font-medium">
{canViewSubscriptions ? formatCompact(summary.total_subscriptions) : "—"}
</span>
</div>
<div className="pt-2 border-t space-y-2">
@@ -244,7 +248,9 @@ export default function PurchasePlansPage() {
<TrendingUp className="h-4 w-4 text-muted-foreground" />
<span className="text-sm text-muted-foreground">CPF</span>
</div>
<span className="font-medium">{formatCurrency(summary.avg_cpf)}</span>
<span className="font-medium">
{canViewSubscriptions ? formatCurrency(summary.avg_cpf) : "—"}
</span>
</div>
</div>
</div>