feat: add projects selector to analytics page
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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) =>
|
||||||
|
|||||||
@@ -29,7 +29,11 @@ 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) {
|
||||||
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