fix: creatre placement fix

This commit is contained in:
ivannoskov
2026-02-02 11:42:03 +03:00
parent 74ef1108e1
commit 066f6a4f5b

View File

@@ -6,7 +6,6 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Badge } from "@/components/ui/badge";
import {
Dialog,
DialogContent,
@@ -27,6 +26,7 @@ import { cn } from "@/lib/utils";
import { parseChannelInput } from "@/lib/utils/channel-parser";
import { placementsApi, creativesApi, channelsApi } from "@/lib/api";
import type { Placement, Creative, Channel } from "@/lib/types/api";
import { Badge } from "./ui/badge";
interface ChannelInput {
username: string;
@@ -64,41 +64,20 @@ export function CreatePlacementsDialog({
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Step 1: Channels
const [newChannelInput, setNewChannelInput] = useState("");
const [selectedChannels, setSelectedChannels] = useState<ChannelInput[]>([]);
const [existingChannels, setExistingChannels] = useState<string[]>([]);
const [projectChannels, setProjectChannels] = useState<Channel[]>([]);
// Step 2: Details
const [creativeId, setCreativeId] = useState<string | null>(prefilledCreativeId);
const [format, setFormat] = useState("");
const [cost, setCost] = useState("");
const [placementDate, setPlacementDate] = useState("");
const [comment, setComment] = useState("");
// Step 3: Confirm
const [creatives, setCreatives] = useState<Creative[]>([]);
const [creating, setCreating] = useState(false);
const [createdCount, setCreatedCount] = useState<number | null>(null);
const existingUsernames = new Set(
existingPlacements
.map((p) => p.channel.username?.toLowerCase())
.filter(Boolean)
);
const getExistingChannelIdByUsername = (usernameLower: string): string | null => {
const matched = existingPlacements.find(
(p) => p.channel.username?.toLowerCase() === usernameLower
);
return matched?.channel.id ?? null;
};
const handleOpenChange = (isOpen: boolean) => {
if (!isOpen) {
resetForm();
}
setInternalOpen(isOpen);
onOpenChange?.(isOpen);
};
@@ -124,7 +103,26 @@ export function CreatePlacementsDialog({
setCreatedCount(null);
};
// Load creatives when dialog opens or when moving to step 2
const existingUsernames = new Set(
existingPlacements
.map((p) => p.channel.username?.toLowerCase())
.filter(Boolean)
);
const getExistingChannelIdByUsername = (usernameLower: string): string | null => {
const matched = existingPlacements.find(
(p) => p.channel.username?.toLowerCase() === usernameLower
);
return matched?.channel.id ?? null;
};
useEffect(() => {
if (isOpen) {
loadCreatives();
loadProjectChannels();
}
}, [isOpen, workspaceId, projectId, prefilledCreativeId]);
const loadCreatives = async () => {
try {
const response = await creativesApi.list(workspaceId, {
@@ -134,7 +132,6 @@ export function CreatePlacementsDialog({
});
setCreatives(response.items);
// Auto-select prefilled creative if set
if (prefilledCreativeId && response.items.length > 0) {
const prefilled = response.items.find((c) => c.id === prefilledCreativeId);
if (prefilled) {
@@ -146,7 +143,6 @@ export function CreatePlacementsDialog({
}
};
// Load project channels when opening dialog
const loadProjectChannels = async () => {
try {
const projectPlacements = await placementsApi.getByProject(workspaceId, projectId);
@@ -157,14 +153,6 @@ export function CreatePlacementsDialog({
}
};
// Load creatives and project channels when dialog opens
useEffect(() => {
if (isOpen) {
loadCreatives();
loadProjectChannels();
}
}, [isOpen, workspaceId, projectId, prefilledCreativeId]);
const addChannel = async () => {
const username = parseChannelInput(newChannelInput);
if (!username) {
@@ -178,7 +166,6 @@ export function CreatePlacementsDialog({
return;
}
// Check if channel exists in project channels
const projectChannel = projectChannels.find((c) => c.username?.toLowerCase() === lowerUsername);
if (projectChannel) {
setSelectedChannels([...selectedChannels, { username, channelId: projectChannel.id }]);
@@ -187,7 +174,6 @@ export function CreatePlacementsDialog({
return;
}
// If channel already has placements in this project, reuse its channel_id.
if (existingUsernames.has(lowerUsername)) {
const existingChannelId = getExistingChannelIdByUsername(lowerUsername);
if (!existingChannelId) {
@@ -200,7 +186,6 @@ export function CreatePlacementsDialog({
return;
}
// Channel doesn't exist in project yet. Create it globally via API to get channel_id.
try {
const createResponse = await channelsApi.create([{ username }]);
const channelId = createResponse.results[0]?.channel.id;
@@ -263,15 +248,13 @@ export function CreatePlacementsDialog({
};
const selectedCreative = creatives.find((c) => c.id === creativeId);
// If open is controlled, don't render DialogTrigger
const isControlled = controlledOpen !== undefined;
return (
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
{!isControlled && (
<DialogTrigger asChild onClick={handleTriggerClick}>{children}</DialogTrigger>
)}
<DialogTrigger asChild onClick={isControlled ? undefined : handleTriggerClick}>
{children}
</DialogTrigger>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Создать размещения</DialogTitle>
@@ -297,7 +280,6 @@ export function CreatePlacementsDialog({
</div>
) : (
<>
{/* Progress Steps */}
<div className="flex items-center justify-center gap-2 mb-6">
{[1, 2, 3].map((s) => (
<div
@@ -323,7 +305,6 @@ export function CreatePlacementsDialog({
))}
</div>
{/* Error */}
{error && (
<div className="flex items-center gap-2 p-3 rounded-md bg-destructive/10 text-destructive text-sm mb-4">
<AlertCircle className="h-4 w-4 shrink-0" />
@@ -331,10 +312,8 @@ export function CreatePlacementsDialog({
</div>
)}
{/* Step 1: Channels */}
{step === 1 && (
<div className="space-y-4">
{/* Add new channel */}
<div className="space-y-2">
<Label>Добавить канал</Label>
<div className="flex gap-2">
@@ -354,7 +333,6 @@ export function CreatePlacementsDialog({
</p>
</div>
{/* Selected channels */}
{selectedChannels.length > 0 && (
<div className="space-y-2">
<Label>Выбранные каналы ({selectedChannels.length})</Label>
@@ -379,7 +357,6 @@ export function CreatePlacementsDialog({
</div>
)}
{/* Existing project channels */}
{projectChannels.length > 0 && (
<div className="space-y-2">
<div className="flex items-center gap-2">
@@ -432,7 +409,6 @@ export function CreatePlacementsDialog({
</div>
)}
{/* Step 2: Details */}
{step === 2 && (
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
@@ -479,21 +455,20 @@ export function CreatePlacementsDialog({
onChange={(e) => setPlacementDate(e.target.value)}
/>
</div>
</div>
<div className="space-y-2">
<Label>Комментарий</Label>
<Textarea
placeholder="Дополнительные заметки..."
value={comment}
onChange={(e) => setComment(e.target.value)}
rows={2}
/>
<div className="col-span-2 space-y-2">
<Label>Комментарий</Label>
<Textarea
placeholder="Дополнительные заметки..."
value={comment}
onChange={(e) => setComment(e.target.value)}
rows={2}
/>
</div>
</div>
</div>
)}
{/* Step 3: Confirm */}
{step === 3 && (
<div className="space-y-4">
<div className="space-y-2">
@@ -547,7 +522,6 @@ export function CreatePlacementsDialog({
</div>
)}
{/* Navigation */}
<DialogFooter>
{step > 1 && (
<Button