feat: update permissions
This commit is contained in:
@@ -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 }) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user