feat: add projects selector to analytics page

This commit is contained in:
ivannoskov
2026-03-04 08:46:53 +03:00
parent 6a7b4dedcd
commit 24f4378d51
3 changed files with 62 additions and 9 deletions

View File

@@ -110,13 +110,7 @@ export default function LoginPage() {
className="w-full" className="w-full"
size="lg" size="lg"
> >
<svg <Image width={30} height={30} alt="telegram" src="/icons/telegram.svg" className="mr-2 h-5 w-5"/>
className="mr-2 h-5 w-5"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm5.562 8.161c-.18.717-.962 3.767-1.362 5.001-.169.523-.506.697-.831.715-.708.064-1.245-.468-1.931-.918-.988-.646-1.547-1.048-2.508-1.678-.111-.073-.336-.218-.325-.409.009-.169.169-.316.373-.503l3.067-2.854c.234-.217.136-.359-.15-.223l-3.845 2.365c-.516.324-.989.472-1.411.461-.675-.018-1.969-.393-2.937-.719-.688-.232-1.236-.354-1.188-.755.025-.211.325-.426.9-.645 4.089-1.781 6.785-2.954 8.09-3.518 3.853-1.636 4.65-1.918 5.17-1.926.115-.002.371.026.536.161.141.114.18.267.198.375.019.108.042.353.024.545z" />
</svg>
Войти через Telegram Войти через Telegram
</Button> </Button>

View File

@@ -796,6 +796,7 @@ export default function ProjectsAnalyticsPage() {
from: subDays(new Date(), 29), from: subDays(new Date(), 29),
to: new Date(), to: new Date(),
})); }));
const [tableProjectIds, setTableProjectIds] = useState<string[]>([]);
const sensors = useSensors( const sensors = useSensors(
useSensor(PointerSensor), useSensor(PointerSensor),
@@ -866,12 +867,13 @@ export default function ProjectsAnalyticsPage() {
// Load table data // Load table data
useEffect(() => { useEffect(() => {
loadTableData(); loadTableData();
}, [workspaceId, tableGrouping, tableDateGrouping, tableDateRange]); }, [workspaceId, tableGrouping, tableDateGrouping, tableDateRange, tableProjectIds]);
const loadTableData = async () => { const loadTableData = async () => {
try { try {
setTableLoading(true); setTableLoading(true);
const response = await demoAwareAnalyticsApi.projects(workspaceId, { const response = await demoAwareAnalyticsApi.projects(workspaceId, {
project_ids: tableProjectIds.length > 0 ? tableProjectIds : undefined,
date_from: tableDateRange?.from date_from: tableDateRange?.from
? startOfDay(tableDateRange.from).toISOString() ? startOfDay(tableDateRange.from).toISOString()
: undefined, : undefined,
@@ -1025,6 +1027,59 @@ export default function ProjectsAnalyticsPage() {
</CardDescription> </CardDescription>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="w-[200px] justify-start">
{tableProjectIds.length === 0
? "Все проекты"
: tableProjectIds.length === 1
? projects.find((p) => p.id === tableProjectIds[0])?.title || "Проект"
: `${tableProjectIds.length} проекта`}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-64 p-2" align="start">
<div className="flex gap-2 mb-2">
<Button
variant="ghost"
size="sm"
className="flex-1 text-xs"
onClick={() => setTableProjectIds([])}
disabled={tableProjectIds.length === 0}
>
Все
</Button>
<Button
variant="ghost"
size="sm"
className="flex-1 text-xs"
onClick={() => setTableProjectIds(projects.map((p) => p.id))}
disabled={tableProjectIds.length === projects.length}
>
Сбросить
</Button>
</div>
<div className="max-h-64 overflow-y-auto border rounded-md p-1">
{projects.map((project) => {
const isSelected = tableProjectIds.includes(project.id);
return (
<div
key={project.id}
className="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-accent cursor-pointer"
onClick={() => {
const newIds = isSelected
? tableProjectIds.filter((id) => id !== project.id)
: [...tableProjectIds, project.id];
setTableProjectIds(newIds);
}}
>
<Checkbox checked={isSelected} />
<span className="text-sm truncate">{project.title}</span>
</div>
);
})}
</div>
</DropdownMenuContent>
</DropdownMenu>
<Select <Select
value={tableGrouping} value={tableGrouping}
onValueChange={(value) => onValueChange={(value) =>

View File

@@ -29,8 +29,12 @@ const buildUrl = (endpoint: string, params?: Record<string, any>): string => {
const searchParams = new URLSearchParams(); const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => { Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) { if (value !== undefined && value !== null) {
if (Array.isArray(value)) {
value.forEach((v) => searchParams.append(key, String(v)));
} else {
searchParams.append(key, String(value)); searchParams.append(key, String(value));
} }
}
}); });
const queryString = searchParams.toString(); const queryString = searchParams.toString();