feat: some fixes

This commit is contained in:
ivannoskov
2026-02-02 11:17:50 +03:00
parent f46a3265c6
commit 74ef1108e1
10 changed files with 945 additions and 262 deletions

View File

@@ -49,11 +49,11 @@ export default function NewPlacementPage() {
const router = useRouter();
const searchParams = useSearchParams();
const workspaceId = params?.workspaceId as string;
const { currentProject } = useWorkspace();
const { currentProject, setSelectedProjects, projects } = useWorkspace();
// Pre-filled from URL params
const prefilledCreativeId = searchParams.get("creative_id");
const prefilledChannelId = searchParams.get("channel_id");
const prefilledCreativeId = searchParams.get("creative_id");
// Form state
const [channelId, setChannelId] = useState(prefilledChannelId || "");
@@ -73,10 +73,26 @@ export default function NewPlacementPage() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
// Auto-select project when creative_id is provided
useEffect(() => {
loadData();
}, [workspaceId, currentProject?.id]);
if (!prefilledCreativeId || projects.length === 0) return;
const loadCreativeAndSelectProject = async () => {
try {
const creative = await creativesApi.get(workspaceId, prefilledCreativeId);
const project = projects.find(p => p.id === creative.project_id);
if (project) {
setSelectedProjects([project]);
}
} catch (err) {
console.error("Failed to load creative:", err);
}
};
loadCreativeAndSelectProject();
}, [prefilledCreativeId, projects, workspaceId, setSelectedProjects]);
// Load channels and creatives
const loadData = async () => {
try {
setLoadingData(true);
@@ -101,6 +117,10 @@ export default function NewPlacementPage() {
}
};
useEffect(() => {
loadData();
}, [workspaceId, currentProject?.id]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -129,7 +149,8 @@ export default function NewPlacementPage() {
invite_link_type: inviteLinkType,
});
router.push(`/dashboard/${workspaceId}/placements/${placement.id}`);
// Redirect to placements list with project filter
router.push(`/dashboard/${workspaceId}/placements?project_id=${currentProject.id}`);
} catch (err: any) {
setError(err?.detail || "Не удалось создать размещение");
} finally {

View File

@@ -5,7 +5,7 @@
// ============================================================================
import { useEffect, useState, useMemo, useCallback } from "react";
import { useParams } from "next/navigation";
import { useParams, useSearchParams } from "next/navigation";
import Link from "next/link";
import {
Loader2,
@@ -22,6 +22,7 @@ import {
FileSpreadsheet,
FileText,
Info,
X,
} from "lucide-react";
import { DashboardHeader } from "@/components/layout/dashboard-header";
import { useWorkspace } from "@/components/providers/workspace-provider";
@@ -254,6 +255,7 @@ const downloadBlob = (blob: Blob, filename: string) => {
export default function PlacementsPage() {
const params = useParams();
const searchParams = useSearchParams();
const workspaceId = params?.workspaceId as string;
const { selectedProjects, getProjectFilterId, allProjectsSelected, hasPermission } = useWorkspace();
const projectFilterId = getProjectFilterId();
@@ -264,6 +266,7 @@ export default function PlacementsPage() {
const [includeArchived, setIncludeArchived] = useState(false);
const [sortField, setSortField] = useState<SortField | null>(null);
const [sortOrder, setSortOrder] = useState<SortOrder>(null);
const [creativeFilterId, setCreativeFilterId] = useState<string | null>(searchParams.get("creative_id"));
// Aggregation settings
const [aggCost, setAggCost] = useState<AggFunc>("sum");
@@ -274,6 +277,11 @@ export default function PlacementsPage() {
const canWrite = hasPermission("placements_write");
useEffect(() => {
const creativeId = searchParams.get("creative_id");
setCreativeFilterId(creativeId);
}, [searchParams]);
useEffect(() => {
loadPlacements();
}, [workspaceId, projectFilterId, includeArchived]);
@@ -321,11 +329,15 @@ export default function PlacementsPage() {
// Filter and sort
const filteredPlacements = useMemo(() => {
return placements
.filter(
(p) =>
.filter((p) => {
const matchesSearch =
p.channel.title.toLowerCase().includes(search.toLowerCase()) ||
(p.creative_id && p.creative_id.toString().toLowerCase().includes(search.toLowerCase()))
)
(p.creative_id && p.creative_id.toString().toLowerCase().includes(search.toLowerCase()));
const matchesCreative = !creativeFilterId || p.creative_id === creativeFilterId;
return matchesSearch && matchesCreative;
})
.sort((a, b) => {
if (!sortField || !sortOrder) return 0;
@@ -460,11 +472,24 @@ export default function PlacementsPage() {
<div className="flex flex-1 flex-col gap-4 p-4 pt-0 mt-4">
<div className="flex items-center justify-between">
<div>
<div className="space-y-1">
<h1 className="text-2xl font-bold tracking-tight">Размещения</h1>
<p className="text-muted-foreground">
{filteredPlacements.length} размещений
</p>
<div className="flex items-center gap-2">
<p className="text-muted-foreground">
{filteredPlacements.length} размещений
</p>
{creativeFilterId && (
<Badge variant="secondary" className="gap-1">
Креатив
<Link
href={`/dashboard/${workspaceId}/placements`}
className="hover:text-destructive"
>
<X className="h-3 w-3" />
</Link>
</Badge>
)}
</div>
</div>
<div className="flex items-center gap-2">
{/* Export Button */}