Dashboard filtering, automatic tracking, chart rebuild & settings modal #40
@@ -191,17 +191,6 @@ export default function Campaigns({ siteId, dateRange, filters }: CampaignsProps
|
|||||||
<PlusIcon className="w-3.5 h-3.5" />
|
<PlusIcon className="w-3.5 h-3.5" />
|
||||||
Build URL
|
Build URL
|
||||||
</Button>
|
</Button>
|
||||||
{showViewAll && (
|
|
||||||
<button
|
|
||||||
onClick={() => setIsModalOpen(true)}
|
|
||||||
className="p-1.5 text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors focus:outline-none focus:ring-2 focus:ring-brand-orange rounded-md cursor-pointer"
|
|
||||||
title="View all"
|
|
||||||
>
|
|
||||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -268,9 +257,21 @@ export default function Campaigns({ siteId, dateRange, filters }: CampaignsProps
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{Array.from({ length: emptySlots }).map((_, i) => (
|
{showViewAll ? (
|
||||||
|
<button
|
||||||
|
onClick={() => setIsModalOpen(true)}
|
||||||
|
className="flex items-center justify-center gap-1.5 h-9 w-full text-xs font-medium text-neutral-400 dark:text-neutral-500 hover:text-brand-orange dark:hover:text-brand-orange transition-colors cursor-pointer rounded-lg px-2 -mx-2"
|
||||||
|
>
|
||||||
|
View all
|
||||||
|
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
Array.from({ length: emptySlots }).map((_, i) => (
|
||||||
<div key={`empty-${i}`} className="h-9 px-2 -mx-2" aria-hidden="true" />
|
<div key={`empty-${i}`} className="h-9 px-2 -mx-2" aria-hidden="true" />
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex-1 min-h-[270px] flex flex-col items-center justify-center text-center px-6 py-8 gap-4">
|
<div className="flex-1 min-h-[270px] flex flex-col items-center justify-center text-center px-6 py-8 gap-4">
|
||||||
|
|||||||
@@ -96,22 +96,9 @@ export default function ContentStats({ topPages, entryPages, exitPages, domain,
|
|||||||
<>
|
<>
|
||||||
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl p-6 h-full flex flex-col">
|
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl p-6 h-full flex flex-col">
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<h3 className="text-lg font-semibold text-neutral-900 dark:text-white">
|
<h3 className="text-lg font-semibold text-neutral-900 dark:text-white">
|
||||||
Content
|
Content
|
||||||
</h3>
|
</h3>
|
||||||
{showViewAll && (
|
|
||||||
<button
|
|
||||||
onClick={() => setIsModalOpen(true)}
|
|
||||||
className="p-1.5 text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors focus:outline-none focus:ring-2 focus:ring-brand-orange rounded-md cursor-pointer"
|
|
||||||
title="View all"
|
|
||||||
>
|
|
||||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-1" role="tablist" aria-label="Content view tabs" onKeyDown={handleTabKeyDown}>
|
<div className="flex gap-1" role="tablist" aria-label="Content view tabs" onKeyDown={handleTabKeyDown}>
|
||||||
{(['top_pages', 'entry_pages', 'exit_pages'] as Tab[]).map((tab) => (
|
{(['top_pages', 'entry_pages', 'exit_pages'] as Tab[]).map((tab) => (
|
||||||
<button
|
<button
|
||||||
@@ -166,9 +153,21 @@ export default function ContentStats({ topPages, entryPages, exitPages, domain,
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{Array.from({ length: emptySlots }).map((_, i) => (
|
{showViewAll ? (
|
||||||
|
<button
|
||||||
|
onClick={() => setIsModalOpen(true)}
|
||||||
|
className="flex items-center justify-center gap-1.5 h-9 w-full text-xs font-medium text-neutral-400 dark:text-neutral-500 hover:text-brand-orange dark:hover:text-brand-orange transition-colors cursor-pointer rounded-lg px-2 -mx-2"
|
||||||
|
>
|
||||||
|
View all
|
||||||
|
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
Array.from({ length: emptySlots }).map((_, i) => (
|
||||||
<div key={`empty-${i}`} className="h-9 px-2 -mx-2" aria-hidden="true" />
|
<div key={`empty-${i}`} className="h-9 px-2 -mx-2" aria-hidden="true" />
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-full flex flex-col items-center justify-center text-center px-6 py-8 gap-3">
|
<div className="h-full flex flex-col items-center justify-center text-center px-6 py-8 gap-3">
|
||||||
|
|||||||
@@ -198,22 +198,9 @@ export default function Locations({ countries, cities, regions, geoDataLevel = '
|
|||||||
<>
|
<>
|
||||||
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl p-6 h-full flex flex-col">
|
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl p-6 h-full flex flex-col">
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<h3 className="text-lg font-semibold text-neutral-900 dark:text-white">
|
<h3 className="text-lg font-semibold text-neutral-900 dark:text-white">
|
||||||
Locations
|
Locations
|
||||||
</h3>
|
</h3>
|
||||||
{showViewAll && (
|
|
||||||
<button
|
|
||||||
onClick={() => setIsModalOpen(true)}
|
|
||||||
className="p-1.5 text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors focus:outline-none focus:ring-2 focus:ring-brand-orange rounded-md cursor-pointer"
|
|
||||||
title="View all"
|
|
||||||
>
|
|
||||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-1" role="tablist" aria-label="Location view tabs" onKeyDown={handleTabKeyDown}>
|
<div className="flex gap-1" role="tablist" aria-label="Location view tabs" onKeyDown={handleTabKeyDown}>
|
||||||
{(['map', 'countries', 'regions', 'cities'] as Tab[]).map((tab) => (
|
{(['map', 'countries', 'regions', 'cities'] as Tab[]).map((tab) => (
|
||||||
<button
|
<button
|
||||||
@@ -284,9 +271,21 @@ export default function Locations({ countries, cities, regions, geoDataLevel = '
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
{Array.from({ length: emptySlots }).map((_, i) => (
|
{showViewAll ? (
|
||||||
|
<button
|
||||||
|
onClick={() => setIsModalOpen(true)}
|
||||||
|
className="flex items-center justify-center gap-1.5 h-9 w-full text-xs font-medium text-neutral-400 dark:text-neutral-500 hover:text-brand-orange dark:hover:text-brand-orange transition-colors cursor-pointer rounded-lg px-2 -mx-2"
|
||||||
|
>
|
||||||
|
View all
|
||||||
|
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
Array.from({ length: emptySlots }).map((_, i) => (
|
||||||
<div key={`empty-${i}`} className="h-9 px-2 -mx-2" aria-hidden="true" />
|
<div key={`empty-${i}`} className="h-9 px-2 -mx-2" aria-hidden="true" />
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-full flex flex-col items-center justify-center text-center px-6 py-8 gap-3">
|
<div className="h-full flex flex-col items-center justify-center text-center px-6 py-8 gap-3">
|
||||||
|
|||||||
@@ -122,22 +122,9 @@ export default function TechSpecs({ browsers, os, devices, screenResolutions, co
|
|||||||
<>
|
<>
|
||||||
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl p-6 h-full flex flex-col">
|
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl p-6 h-full flex flex-col">
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<h3 className="text-lg font-semibold text-neutral-900 dark:text-white">
|
<h3 className="text-lg font-semibold text-neutral-900 dark:text-white">
|
||||||
Technology
|
Technology
|
||||||
</h3>
|
</h3>
|
||||||
{showViewAll && (
|
|
||||||
<button
|
|
||||||
onClick={() => setIsModalOpen(true)}
|
|
||||||
className="p-1.5 text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors focus:outline-none focus:ring-2 focus:ring-brand-orange rounded-md cursor-pointer"
|
|
||||||
title="View all"
|
|
||||||
>
|
|
||||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-1" role="tablist" aria-label="Technology view tabs" onKeyDown={handleTabKeyDown}>
|
<div className="flex gap-1" role="tablist" aria-label="Technology view tabs" onKeyDown={handleTabKeyDown}>
|
||||||
{(['browsers', 'os', 'devices', 'screens'] as Tab[]).map((tab) => (
|
{(['browsers', 'os', 'devices', 'screens'] as Tab[]).map((tab) => (
|
||||||
<button
|
<button
|
||||||
@@ -188,9 +175,21 @@ export default function TechSpecs({ browsers, os, devices, screenResolutions, co
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
{Array.from({ length: emptySlots }).map((_, i) => (
|
{showViewAll ? (
|
||||||
|
<button
|
||||||
|
onClick={() => setIsModalOpen(true)}
|
||||||
|
className="flex items-center justify-center gap-1.5 h-9 w-full text-xs font-medium text-neutral-400 dark:text-neutral-500 hover:text-brand-orange dark:hover:text-brand-orange transition-colors cursor-pointer rounded-lg px-2 -mx-2"
|
||||||
|
>
|
||||||
|
View all
|
||||||
|
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
Array.from({ length: emptySlots }).map((_, i) => (
|
||||||
<div key={`empty-${i}`} className="h-9 px-2 -mx-2" aria-hidden="true" />
|
<div key={`empty-${i}`} className="h-9 px-2 -mx-2" aria-hidden="true" />
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-full flex flex-col items-center justify-center text-center px-6 py-8 gap-3">
|
<div className="h-full flex flex-col items-center justify-center text-center px-6 py-8 gap-3">
|
||||||
|
|||||||
@@ -88,17 +88,6 @@ export default function TopReferrers({ referrers, collectReferrers = true, siteI
|
|||||||
<h3 className="text-lg font-semibold text-neutral-900 dark:text-white">
|
<h3 className="text-lg font-semibold text-neutral-900 dark:text-white">
|
||||||
Top Referrers
|
Top Referrers
|
||||||
</h3>
|
</h3>
|
||||||
{showViewAll && (
|
|
||||||
<button
|
|
||||||
onClick={() => setIsModalOpen(true)}
|
|
||||||
className="p-1.5 text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors focus:outline-none focus:ring-2 focus:ring-brand-orange rounded-md cursor-pointer"
|
|
||||||
title="View all"
|
|
||||||
>
|
|
||||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2 flex-1 min-h-[270px]">
|
<div className="space-y-2 flex-1 min-h-[270px]">
|
||||||
@@ -128,9 +117,21 @@ export default function TopReferrers({ referrers, collectReferrers = true, siteI
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{Array.from({ length: emptySlots }).map((_, i) => (
|
{showViewAll ? (
|
||||||
|
<button
|
||||||
|
onClick={() => setIsModalOpen(true)}
|
||||||
|
className="flex items-center justify-center gap-1.5 h-9 w-full text-xs font-medium text-neutral-400 dark:text-neutral-500 hover:text-brand-orange dark:hover:text-brand-orange transition-colors cursor-pointer rounded-lg px-2 -mx-2"
|
||||||
|
>
|
||||||
|
View all
|
||||||
|
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
Array.from({ length: emptySlots }).map((_, i) => (
|
||||||
<div key={`empty-${i}`} className="h-9 px-2 -mx-2" aria-hidden="true" />
|
<div key={`empty-${i}`} className="h-9 px-2 -mx-2" aria-hidden="true" />
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-full flex flex-col items-center justify-center text-center px-6 py-8 gap-3">
|
<div className="h-full flex flex-col items-center justify-center text-center px-6 py-8 gap-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user