feat: update sidebar ui
This commit is contained in:
5
app/dashboard/[workspaceId]/analytics/page.tsx
Normal file
5
app/dashboard/[workspaceId]/analytics/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { redirect } from "next/navigation"
|
||||||
|
|
||||||
|
export default function AnalyticsPage({ params }: { params: { workspaceId: string } }) {
|
||||||
|
redirect(`/dashboard/${params.workspaceId}/analytics/projects`)
|
||||||
|
}
|
||||||
@@ -127,7 +127,7 @@ const getCreativeWord = (count: number): string => {
|
|||||||
export default function CreativesPage() {
|
export default function CreativesPage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const workspaceId = params?.workspaceId as string;
|
const workspaceId = params?.workspaceId as string;
|
||||||
const { selectedProjects, getProjectFilterId, allProjectsSelected, hasPermission } = useWorkspace();
|
const { selectedProjects, getProjectFilterId, allProjectsSelected, hasPermission, currentProject } = useWorkspace();
|
||||||
const projectFilterId = getProjectFilterId();
|
const projectFilterId = getProjectFilterId();
|
||||||
|
|
||||||
const [creatives, setCreatives] = useState<Creative[]>([]);
|
const [creatives, setCreatives] = useState<Creative[]>([]);
|
||||||
@@ -613,22 +613,25 @@ export default function CreativesPage() {
|
|||||||
<li>Креатив автоматически отобразится на платформе</li>
|
<li>Креатив автоматически отобразится на платформе</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-2 pt-2">
|
<div className="flex justify-end gap-2 pt-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setShowCreateDialog(false)}
|
onClick={() => setShowCreateDialog(false)}
|
||||||
>
|
>
|
||||||
Понятно
|
Понятно
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open(`https://t.me/${BOT_USERNAME}`, "_blank");
|
const botUrl = currentProject
|
||||||
setShowCreateDialog(false);
|
? `https://t.me/${BOT_USERNAME}?start=project_${currentProject.id}_createcreative`
|
||||||
}}
|
: `https://t.me/${BOT_USERNAME}`;
|
||||||
>
|
window.open(botUrl, "_blank");
|
||||||
Открыть бота
|
setShowCreateDialog(false);
|
||||||
</Button>
|
}}
|
||||||
</div>
|
>
|
||||||
|
Открыть бота
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -342,8 +342,8 @@ export default function PurchasePlanDetailPage() {
|
|||||||
<DashboardHeader
|
<DashboardHeader
|
||||||
breadcrumbs={[
|
breadcrumbs={[
|
||||||
{ label: "Главная", href: `/dashboard/${workspaceId}` },
|
{ label: "Главная", href: `/dashboard/${workspaceId}` },
|
||||||
{ label: "Планы закупок", href: `/dashboard/${workspaceId}/purchase-plans` },
|
{ label: "Планы закупов", href: `/dashboard/${workspaceId}/purchase-plans` },
|
||||||
{ label: project?.title || "План закупок" },
|
{ label: project?.title || "План закупов" },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -360,7 +360,7 @@ export default function PurchasePlanDetailPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold tracking-tight">
|
<h1 className="text-2xl font-bold tracking-tight">
|
||||||
План закупок: {project?.title}
|
План закупов: {project?.title}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
{stats.totalChannels} каналов • Планируемый бюджет:{" "}
|
{stats.totalChannels} каналов • Планируемый бюджет:{" "}
|
||||||
@@ -577,7 +577,7 @@ export default function PurchasePlanDetailPage() {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Добавить канал в план</DialogTitle>
|
<DialogTitle>Добавить канал в план</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Выберите канал из каталога для добавления в план закупок
|
Выберите канал из каталога для добавления в план закупов
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
@@ -723,7 +723,7 @@ export default function PurchasePlanDetailPage() {
|
|||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>Удалить канал из плана?</AlertDialogTitle>
|
<AlertDialogTitle>Удалить канал из плана?</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
Канал "{deletingChannel?.channel.title}" будет удалён из плана закупок.
|
Канал "{deletingChannel?.channel.title}" будет удалён из плана закупов.
|
||||||
Это действие нельзя отменить.
|
Это действие нельзя отменить.
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
|
|||||||
@@ -116,16 +116,16 @@ export default function PurchasePlansPage() {
|
|||||||
<DashboardHeader
|
<DashboardHeader
|
||||||
breadcrumbs={[
|
breadcrumbs={[
|
||||||
{ label: "Главная", href: `/dashboard/${workspaceId}` },
|
{ label: "Главная", href: `/dashboard/${workspaceId}` },
|
||||||
{ label: "Планы закупок" },
|
{ label: "Планы закупов" },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex flex-1 flex-col gap-4 p-4 pt-0 mt-4">
|
<div className="flex flex-1 flex-col gap-4 p-4 pt-0 mt-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold tracking-tight">Планы закупок</h1>
|
<h1 className="text-2xl font-bold tracking-tight">Планы закупов</h1>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Управление планами закупок по проектам
|
Управление планами закупов по проектам
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,7 +140,7 @@ export default function PurchasePlansPage() {
|
|||||||
<ClipboardList className="h-12 w-12 text-muted-foreground mb-4" />
|
<ClipboardList className="h-12 w-12 text-muted-foreground mb-4" />
|
||||||
<h3 className="text-lg font-semibold mb-2">Нет проектов</h3>
|
<h3 className="text-lg font-semibold mb-2">Нет проектов</h3>
|
||||||
<p className="text-muted-foreground text-center mb-4">
|
<p className="text-muted-foreground text-center mb-4">
|
||||||
Добавьте проект через Telegram бота для создания плана закупок
|
Добавьте проект через Telegram бота для создания плана закупов
|
||||||
</p>
|
</p>
|
||||||
<Button asChild>
|
<Button asChild>
|
||||||
<Link href={`/dashboard/${workspaceId}/projects`}>
|
<Link href={`/dashboard/${workspaceId}/projects`}>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import * as React from "react";
|
|||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import {
|
import {
|
||||||
|
ArrowUpRight,
|
||||||
BarChart3,
|
BarChart3,
|
||||||
BookmarkCheck,
|
BookmarkCheck,
|
||||||
Building2,
|
Building2,
|
||||||
@@ -128,19 +129,12 @@ interface HelpItem {
|
|||||||
action?: () => void;
|
action?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SupportLink {
|
|
||||||
title: string;
|
|
||||||
icon: LucideIcon;
|
|
||||||
href: string;
|
|
||||||
external?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TRAINING_STEPS: TrainingStep[] = [
|
const TRAINING_STEPS: TrainingStep[] = [
|
||||||
{
|
{
|
||||||
id: "dashboard",
|
id: "dashboard",
|
||||||
title: "Главная панель",
|
title: "Главная панель",
|
||||||
description:
|
description:
|
||||||
"Это стартовая точка: отсюда видно общее состояние закупок, статус проектов и свежие уведомления. Сюда возвращаемся, чтобы понять, что делать дальше и какие задачи горят.",
|
"Это стартовая точка: отсюда видно общее состояние закупов, статус проектов и свежие уведомления. Сюда возвращаемся, чтобы понять, что делать дальше и какие задачи горят.",
|
||||||
navItemTitle: "Главная",
|
navItemTitle: "Главная",
|
||||||
icon: LayoutDashboard,
|
icon: LayoutDashboard,
|
||||||
sections: [
|
sections: [
|
||||||
@@ -509,53 +503,50 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|||||||
}, [isTrainingOpen, currentTrainingStep]);
|
}, [isTrainingOpen, currentTrainingStep]);
|
||||||
const isTrainingVisible = isTrainingOpen && Boolean(currentTrainingStep);
|
const isTrainingVisible = isTrainingOpen && Boolean(currentTrainingStep);
|
||||||
|
|
||||||
const supportLinks = React.useMemo<SupportLink[]>(
|
const channelLink = React.useMemo<SupportLink>(
|
||||||
|
() => ({
|
||||||
|
title: "Наш канал",
|
||||||
|
href: "https://t.me/+xqHlRelJ2etkMGIy",
|
||||||
|
icon: Megaphone,
|
||||||
|
external: true,
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const helpSubItems = React.useMemo<HelpItem[]>(
|
||||||
() => [
|
() => [
|
||||||
{
|
|
||||||
title: "Наш канал",
|
|
||||||
href: "https://t.me/+xqHlRelJ2etkMGIy",
|
|
||||||
icon: Megaphone,
|
|
||||||
external: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "Поддержка",
|
title: "Поддержка",
|
||||||
|
description: "Свяжитесь с нашей службой поддержки.",
|
||||||
href: "https://t.me/smartpost_help_bot",
|
href: "https://t.me/smartpost_help_bot",
|
||||||
icon: MessageCircle,
|
icon: MessageCircle,
|
||||||
external: true,
|
external: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "База знаний",
|
title: "База знаний",
|
||||||
|
description: "Документация и инструкции по использованию.",
|
||||||
href: "https://docs.smartpost.ru",
|
href: "https://docs.smartpost.ru",
|
||||||
icon: BookOpen,
|
icon: BookOpen,
|
||||||
external: true,
|
external: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Демо аккаунт",
|
||||||
|
description: "Посмотрите пример рабочего пространства.",
|
||||||
|
href: buildRoute.dashboard(DEMO_WORKSPACE_ID),
|
||||||
|
icon: Sparkles,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const extraHelpItems = React.useMemo<HelpItem[]>(
|
const renderHelpSubItem = React.useCallback((item: HelpItem) => {
|
||||||
() => [
|
|
||||||
{
|
|
||||||
title: "Демо дашборд",
|
|
||||||
description: "Посмотрите пример рабочего пространства со всеми разделами.",
|
|
||||||
href: buildRoute.dashboard(DEMO_WORKSPACE_ID),
|
|
||||||
icon: Sparkles,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Открыть обучение",
|
|
||||||
description: "Запустите интерактивный тур по основным разделам.",
|
|
||||||
action: openTraining,
|
|
||||||
icon: MonitorPlay,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[openTraining]
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderExtraHelpItem = React.useCallback((item: HelpItem) => {
|
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
<item.icon className="mt-1.5 size-3 shrink-0 text-muted-foreground" />
|
<item.icon className="mt-1.5 size-3 shrink-0 !text-muted-foreground/90" />
|
||||||
<p className="text-xs font-light mt-1.5">{item.title}</p>
|
<p className="text-xs font-light mt-1.5 text-muted-foreground/90 flex-1">{item.title}</p>
|
||||||
|
{item.external && (
|
||||||
|
<ArrowUpRight className="mt-1.5 size-3 shrink-0 !text-muted-foreground/60" />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -639,17 +630,17 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|||||||
style={expandedLogoDimensions}
|
style={expandedLogoDimensions}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src="/logo_full_dark.webp"
|
src="/logo_full_dark.png"
|
||||||
width={expandedLogoWidth}
|
width={expandedLogoWidth*2}
|
||||||
height={expandedLogoHeight}
|
height={expandedLogoHeight*2}
|
||||||
alt="Smartpost"
|
alt="Smartpost"
|
||||||
className="shrink-0 dark:hidden"
|
className="shrink-0 dark:hidden"
|
||||||
style={expandedLogoDimensions}
|
style={expandedLogoDimensions}
|
||||||
/>
|
/>
|
||||||
<Image
|
<Image
|
||||||
src="/logo_full_white_2.png"
|
src="/logo_full_white.png"
|
||||||
width={expandedLogoWidth}
|
width={expandedLogoWidth*2}
|
||||||
height={expandedLogoHeight}
|
height={expandedLogoHeight*2}
|
||||||
alt="Smartpost"
|
alt="Smartpost"
|
||||||
className="hidden shrink-0 dark:block"
|
className="hidden shrink-0 dark:block"
|
||||||
style={expandedLogoDimensions}
|
style={expandedLogoDimensions}
|
||||||
@@ -675,33 +666,40 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|||||||
<SidebarGroup className="px-0">
|
<SidebarGroup className="px-0">
|
||||||
<SidebarGroupContent>
|
<SidebarGroupContent>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{supportLinks.map((link) => (
|
{/* Наш канал */}
|
||||||
<SidebarMenuItem key={link.title}>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
asChild
|
asChild
|
||||||
size="sm"
|
size="sm"
|
||||||
tooltip={isSidebarCollapsed ? link.title : undefined}
|
tooltip={isSidebarCollapsed ? channelLink.title : undefined}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={channelLink.href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
{link.external ? (
|
<channelLink.icon className="size-4 text-muted-foreground/90" />
|
||||||
<a
|
<span className="text-muted-foreground/90">{channelLink.title}</span>
|
||||||
href={link.href}
|
<ArrowUpRight className="ml-auto size-3.5 text-muted-foreground/60" />
|
||||||
target="_blank"
|
</a>
|
||||||
rel="noopener noreferrer"
|
</SidebarMenuButton>
|
||||||
className="flex items-center gap-2"
|
</SidebarMenuItem>
|
||||||
>
|
|
||||||
<link.icon className="size-4" />
|
|
||||||
<span>{link.title}</span>
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<Link href={link.href} className="flex items-center gap-2">
|
|
||||||
<link.icon className="size-4" />
|
|
||||||
<span>{link.title}</span>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
))}
|
|
||||||
|
|
||||||
|
{/* Обучение */}
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton
|
||||||
|
size="sm"
|
||||||
|
tooltip={isSidebarCollapsed ? "Обучение" : undefined}
|
||||||
|
onClick={openTraining}
|
||||||
|
className="cursor-pointer"
|
||||||
|
>
|
||||||
|
<MonitorPlay className="size-4 text-muted-foreground/90" />
|
||||||
|
<span className="text-muted-foreground/90">Обучение</span>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
|
||||||
|
{/* Помощь с подменю */}
|
||||||
{isSidebarCollapsed ? (
|
{isSidebarCollapsed ? (
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@@ -712,10 +710,10 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|||||||
className="justify-between"
|
className="justify-between"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<LifeBuoy className="size-4" />
|
<LifeBuoy className="size-4 text-muted-foreground/90" />
|
||||||
<span>Помощь</span>
|
<span className="text-muted-foreground/90">Помощь</span>
|
||||||
</div>
|
</div>
|
||||||
<ChevronRight className="size-4 opacity-60" />
|
<ChevronRight className="size-4 text-muted-foreground/60" />
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
@@ -723,9 +721,9 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|||||||
align="start"
|
align="start"
|
||||||
className="min-w-[280px] space-y-1"
|
className="min-w-[280px] space-y-1"
|
||||||
>
|
>
|
||||||
{extraHelpItems.map((item) => (
|
{helpSubItems.map((item) => (
|
||||||
<DropdownMenuItem key={item.title} asChild>
|
<DropdownMenuItem key={item.title} asChild>
|
||||||
{renderExtraHelpItem(item)}
|
{renderHelpSubItem(item)}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
@@ -740,18 +738,18 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|||||||
className="justify-between"
|
className="justify-between"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<LifeBuoy className="size-4" />
|
<LifeBuoy className="size-4 text-muted-foreground/90" />
|
||||||
<span>Помощь</span>
|
<span className="text-muted-foreground/90">Помощь</span>
|
||||||
</div>
|
</div>
|
||||||
<ChevronRight className="size-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
<ChevronRight className="size-4 text-muted-foreground/60 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<SidebarMenuSub>
|
<SidebarMenuSub>
|
||||||
{extraHelpItems.map((item) => (
|
{helpSubItems.map((item) => (
|
||||||
<SidebarMenuSubItem key={item.title}>
|
<SidebarMenuSubItem key={item.title}>
|
||||||
<SidebarMenuSubButton asChild className="">
|
<SidebarMenuSubButton asChild className="">
|
||||||
{renderExtraHelpItem(item)}
|
{renderHelpSubItem(item)}
|
||||||
</SidebarMenuSubButton>
|
</SidebarMenuSubButton>
|
||||||
</SidebarMenuSubItem>
|
</SidebarMenuSubItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ export function NavMain({
|
|||||||
const hasActiveSubmenu =
|
const hasActiveSubmenu =
|
||||||
hasSubmenu && item.items!.some((sub) => pathname === sub.url);
|
hasSubmenu && item.items!.some((sub) => pathname === sub.url);
|
||||||
const shouldForceOpen = guideState?.expandedItems?.includes(item.title);
|
const shouldForceOpen = guideState?.expandedItems?.includes(item.title);
|
||||||
|
const shouldHighlightButton = item.title === "Аналитика"
|
||||||
|
? false
|
||||||
|
: isActive;
|
||||||
const isGuideHighlight = highlightedItem === item.title;
|
const isGuideHighlight = highlightedItem === item.title;
|
||||||
const highlightedButtonClass = isGuideHighlight
|
const highlightedButtonClass = isGuideHighlight
|
||||||
? "ring-2 ring-primary/70"
|
? "ring-2 ring-primary/70"
|
||||||
@@ -77,19 +80,27 @@ export function NavMain({
|
|||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
tooltip={item.title}
|
tooltip={item.title}
|
||||||
isActive={isActive || hasActiveSubmenu}
|
isActive={shouldHighlightButton}
|
||||||
className={cn(highlightedButtonClass)}
|
className={cn(highlightedButtonClass)}
|
||||||
>
|
>
|
||||||
{item.icon && (
|
{item.icon && (
|
||||||
<item.icon
|
<item.icon
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-4 w-4",
|
"h-4 w-4 transition-colors duration-200",
|
||||||
// (isActive || hasActiveSubmenu) &&
|
shouldHighlightButton
|
||||||
// "text-[#2B1D49] dark:text-[#5F31F4]"
|
? "text-[hsl(262_83%_65%)] drop-shadow-[0_0_8px_rgba(147_51_234_0.25)]"
|
||||||
|
: "text-muted-foreground/90"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<span>{item.title}</span>
|
<span
|
||||||
|
className={cn(
|
||||||
|
"transition-colors duration-200",
|
||||||
|
shouldHighlightButton
|
||||||
|
? "text-foreground font-medium"
|
||||||
|
: "text-muted-foreground/90"
|
||||||
|
)}
|
||||||
|
>{item.title}</span>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
@@ -104,23 +115,28 @@ export function NavMain({
|
|||||||
<Link
|
<Link
|
||||||
href={subItem.url}
|
href={subItem.url}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-2",
|
"flex items-center gap-2 transition-colors duration-200",
|
||||||
isSubActive
|
isSubActive
|
||||||
? "bg-accent text-accent-foreground"
|
? "bg-accent text-accent-foreground"
|
||||||
: ""
|
: "text-muted-foreground hover:text-foreground"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{subItem.icon && (
|
{subItem.icon && (
|
||||||
<subItem.icon
|
<subItem.icon
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-4 w-4",
|
"h-4 w-4 transition-colors duration-200",
|
||||||
// isSubActive &&
|
isSubActive
|
||||||
// "text-[#2B1D49] dark:text-[#5F31F4]"
|
? "!text-[hsl(262_83%_65%)] drop-shadow-[0_0_8px_rgba(147_51_234_0.25)]"
|
||||||
)}
|
: "!text-muted-foreground/90"
|
||||||
/>
|
)}
|
||||||
)}
|
/>
|
||||||
<span>{subItem.title}</span>
|
)}
|
||||||
</Link>
|
<span className={cn(
|
||||||
|
isSubActive
|
||||||
|
? "text-foreground font-medium"
|
||||||
|
: "text-muted-foreground/90"
|
||||||
|
)}>{subItem.title}</span>
|
||||||
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -144,47 +160,71 @@ export function NavMain({
|
|||||||
{hasSubmenu ? (
|
{hasSubmenu ? (
|
||||||
<>
|
<>
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
tooltip={item.title}
|
tooltip={item.title}
|
||||||
isActive={isActive || hasActiveSubmenu}
|
isActive={shouldHighlightButton}
|
||||||
className={cn(highlightedButtonClass)}
|
className={cn(highlightedButtonClass)}
|
||||||
>
|
>
|
||||||
{item.icon && (
|
{item.icon && (
|
||||||
<item.icon
|
<item.icon
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-4 w-4",
|
"h-4 w-4 transition-colors duration-200",
|
||||||
// (isActive || hasActiveSubmenu) &&
|
shouldHighlightButton
|
||||||
// "text-[#2B1D49] dark:text-[#5F31F4]"
|
? "text-[hsl(262_83%_65%)] drop-shadow-[0_0_8px_rgba(147_51_234_0.25)]"
|
||||||
)}
|
: "text-muted-foreground/90"
|
||||||
/>
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"transition-colors duration-200",
|
||||||
|
shouldHighlightButton
|
||||||
|
? "text-foreground font-medium"
|
||||||
|
: "text-muted-foreground/90"
|
||||||
)}
|
)}
|
||||||
<span>{item.title}</span>
|
>{item.title}</span>
|
||||||
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
<ChevronRight
|
||||||
</SidebarMenuButton>
|
className={cn(
|
||||||
</CollapsibleTrigger>
|
"ml-auto transition-transform duration-200",
|
||||||
|
shouldHighlightButton
|
||||||
|
? "text-[hsl(262_83%_65%)]"
|
||||||
|
: "text-muted-foreground/60",
|
||||||
|
"group-data-[state=open]/collapsible:rotate-90"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<SidebarMenuSub>
|
<SidebarMenuSub>
|
||||||
{item.items!.map((subItem) => {
|
{item.items!.map((subItem) => {
|
||||||
const isSubActive = pathname === subItem.url;
|
const isSubActive = pathname === subItem.url;
|
||||||
return (
|
return (
|
||||||
<SidebarMenuSubItem key={subItem.title}>
|
<SidebarMenuSubItem key={subItem.title}>
|
||||||
<SidebarMenuSubButton
|
<SidebarMenuSubButton
|
||||||
asChild
|
asChild
|
||||||
isActive={isSubActive}
|
isActive={isSubActive}
|
||||||
>
|
>
|
||||||
<Link href={subItem.url} className="flex items-center gap-2">
|
<Link href={subItem.url} className="flex items-center gap-2">
|
||||||
{subItem.icon && (
|
{subItem.icon && (
|
||||||
<subItem.icon
|
<subItem.icon
|
||||||
|
className={cn(
|
||||||
|
"h-4 w-4 transition-colors duration-200",
|
||||||
|
isSubActive
|
||||||
|
? "!text-[hsl(262_83%_65%)] drop-shadow-[0_0_8px_rgba(147_51_234_0.25)]"
|
||||||
|
: "!text-muted-foreground/90"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-4 w-4",
|
"transition-colors duration-200",
|
||||||
// isSubActive &&
|
isSubActive
|
||||||
// "text-[#2B1D49] dark:text-[#5F31F4]"
|
? "text-foreground font-medium"
|
||||||
|
: "text-muted-foreground/90"
|
||||||
)}
|
)}
|
||||||
/>
|
>{subItem.title}</span>
|
||||||
)}
|
</Link>
|
||||||
<span>{subItem.title}</span>
|
</SidebarMenuSubButton>
|
||||||
</Link>
|
|
||||||
</SidebarMenuSubButton>
|
|
||||||
</SidebarMenuSubItem>
|
</SidebarMenuSubItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -195,20 +235,28 @@ export function NavMain({
|
|||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
asChild
|
asChild
|
||||||
tooltip={item.title}
|
tooltip={item.title}
|
||||||
isActive={isActive}
|
isActive={shouldHighlightButton}
|
||||||
className={cn(highlightedButtonClass)}
|
className={cn(highlightedButtonClass)}
|
||||||
>
|
>
|
||||||
<Link href={item.url} className="flex items-center gap-2">
|
<Link href={item.url} className="flex items-center gap-2">
|
||||||
{item.icon && (
|
{item.icon && (
|
||||||
<item.icon
|
<item.icon
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-4 w-4",
|
"h-4 w-4 transition-colors duration-200",
|
||||||
// isActive &&
|
shouldHighlightButton
|
||||||
// "text-[#2B1D49] dark:text-[#5F31F4]"
|
? "text-[hsl(262_83%_65%)] drop-shadow-[0_0_8px_rgba(147_51_234_0.25)]"
|
||||||
|
: "text-muted-foreground/90"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<span>{item.title}</span>
|
<span
|
||||||
|
className={cn(
|
||||||
|
"transition-colors duration-200",
|
||||||
|
shouldHighlightButton
|
||||||
|
? "text-foreground font-medium"
|
||||||
|
: "text-muted-foreground/90"
|
||||||
|
)}
|
||||||
|
>{item.title}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const buildRoute = {
|
|||||||
`/dashboard/${workspaceId}/placements/${placementId}`,
|
`/dashboard/${workspaceId}/placements/${placementId}`,
|
||||||
|
|
||||||
// Analytics
|
// Analytics
|
||||||
analytics: (workspaceId: string) => `/dashboard/${workspaceId}/analytics/projects`,
|
analytics: (workspaceId: string) => `/dashboard/${workspaceId}/analytics`,
|
||||||
analyticsProjects: (workspaceId: string) =>
|
analyticsProjects: (workspaceId: string) =>
|
||||||
`/dashboard/${workspaceId}/analytics/projects`,
|
`/dashboard/${workspaceId}/analytics/projects`,
|
||||||
analyticsCreatives: (workspaceId: string) =>
|
analyticsCreatives: (workspaceId: string) =>
|
||||||
|
|||||||
@@ -228,4 +228,4 @@ CPM — Cost Per Mile — метрика отражающая стоимость
|
|||||||
|
|
||||||
* Отображать средний CPF в списках закупов, креативов и внешних каналов
|
* Отображать средний CPF в списках закупов, креативов и внешних каналов
|
||||||
* Отображать средний CPM в списках закупов, креативов и внешних каналов
|
* Отображать средний CPM в списках закупов, креативов и внешних каналов
|
||||||
* Отображать столбчатую диаграмму с объемом закупок с группировкой по дате (день, неделя, месяц, квартал, год) и возможностью фильтрации по целевым каналам на отдельном экране “Аналитика” → “Затраты”
|
* Отображать столбчатую диаграмму с объемом закупов с группировкой по дате (день, неделя, месяц, квартал, год) и возможностью фильтрации по целевым каналам на отдельном экране “Аналитика” → “Затраты”
|
||||||
|
|||||||
Reference in New Issue
Block a user