fix: creatre placement fix
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user