feat: add projects selector to analytics page
This commit is contained in:
@@ -110,13 +110,7 @@ export default function LoginPage() {
|
||||
className="w-full"
|
||||
size="lg"
|
||||
>
|
||||
<svg
|
||||
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>
|
||||
<Image width={30} height={30} alt="telegram" src="/icons/telegram.svg" className="mr-2 h-5 w-5"/>
|
||||
Войти через Telegram
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -796,6 +796,7 @@ export default function ProjectsAnalyticsPage() {
|
||||
from: subDays(new Date(), 29),
|
||||
to: new Date(),
|
||||
}));
|
||||
const [tableProjectIds, setTableProjectIds] = useState<string[]>([]);
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor),
|
||||
@@ -866,12 +867,13 @@ export default function ProjectsAnalyticsPage() {
|
||||
// Load table data
|
||||
useEffect(() => {
|
||||
loadTableData();
|
||||
}, [workspaceId, tableGrouping, tableDateGrouping, tableDateRange]);
|
||||
}, [workspaceId, tableGrouping, tableDateGrouping, tableDateRange, tableProjectIds]);
|
||||
|
||||
const loadTableData = async () => {
|
||||
try {
|
||||
setTableLoading(true);
|
||||
const response = await demoAwareAnalyticsApi.projects(workspaceId, {
|
||||
project_ids: tableProjectIds.length > 0 ? tableProjectIds : undefined,
|
||||
date_from: tableDateRange?.from
|
||||
? startOfDay(tableDateRange.from).toISOString()
|
||||
: undefined,
|
||||
@@ -1025,6 +1027,59 @@ export default function ProjectsAnalyticsPage() {
|
||||
</CardDescription>
|
||||
</div>
|
||||
<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
|
||||
value={tableGrouping}
|
||||
onValueChange={(value) =>
|
||||
|
||||
@@ -29,7 +29,11 @@ const buildUrl = (endpoint: string, params?: Record<string, any>): string => {
|
||||
const searchParams = new URLSearchParams();
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null) {
|
||||
searchParams.append(key, String(value));
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((v) => searchParams.append(key, String(v)));
|
||||
} else {
|
||||
searchParams.append(key, String(value));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user