Files
tgex-frontend/components/creative-select-dialog.tsx
2026-03-04 08:29:11 +03:00

165 lines
4.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from "react";
import { X, Image, MousePointerClick } from "lucide-react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import type { Creative } from "@/lib/types/api";
interface CreativeMediaItem {
media_type: string;
media_file_id: string;
position: number;
s3_url?: string | null;
}
interface CreativeButton {
text: string;
url: string;
}
interface CreativeSelectDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
creatives: {
id: string;
name: string;
text: string;
media_items: CreativeMediaItem[];
buttons: CreativeButton[];
}[];
onSelect: (creative: { id: string; name: string; thumbnail_url?: string | null }) => void;
onClear: (() => void) | null;
currentCreativeId: string | null | undefined;
}
function CreativeCard({
creative,
isSelected,
onClick,
}: {
creative: {
id: string;
name: string;
text: string;
media_items: CreativeMediaItem[];
buttons: CreativeButton[];
};
isSelected: boolean;
onClick: () => void;
}) {
const thumbnailUrl = creative.media_items?.[0]?.s3_url;
const imagesCount = creative.media_items?.length || 0;
const buttonsCount = creative.buttons?.length || 0;
const cleanText = creative.text?.replace(/<[^>]+>/g, '') || '';
const truncatedText = cleanText.substring(0, 80) + (cleanText.length > 80 ? '...' : '');
return (
<div
className={cn(
"flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-all",
isSelected
? "border-primary bg-primary/5 ring-1 ring-primary"
: "hover:bg-muted hover:border-muted-foreground/30"
)}
onClick={onClick}
>
{thumbnailUrl ? (
<div className="w-14 h-14 rounded-lg bg-muted flex items-center justify-center overflow-hidden shrink-0">
<img
src={thumbnailUrl}
alt=""
className="w-full h-full object-cover"
/>
</div>
) : (
<></>
)}
<div className="flex-1 min-w-0 space-y-1.5">
<div className="flex items-center gap-2">
{imagesCount > 0 && (
<span className="flex items-center gap-1 text-xs text-muted-foreground bg-muted px-1.5 py-0.5 rounded">
<Image className="h-3 w-3" />
{imagesCount}
</span>
)}
{buttonsCount > 0 && (
<span className="flex items-center gap-1 text-xs text-muted-foreground bg-muted px-1.5 py-0.5 rounded">
<MousePointerClick className="h-3 w-3" />
{buttonsCount}
</span>
)}
</div>
<p className="text-sm text-foreground line-clamp-2 leading-snug">
{truncatedText || 'Без текста'}
</p>
</div>
</div>
);
}
export function CreativeSelectDialog({
open,
onOpenChange,
creatives,
onSelect,
onClear,
currentCreativeId,
}: CreativeSelectDialogProps) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-lg max-h-[85vh] flex flex-col">
<DialogHeader className="shrink-0">
<DialogTitle>Выберите креатив</DialogTitle>
<p className="text-sm text-muted-foreground">
{creatives.length} креативов доступно
</p>
</DialogHeader>
{creatives.length === 0 ? (
<div className="text-center py-8 text-muted-foreground">
Нет доступных креативов
</div>
) : (
<div className="flex-1 overflow-y-auto space-y-2 pr-1">
{creatives.map((creative) => (
<CreativeCard
key={creative.id}
creative={creative}
isSelected={currentCreativeId === creative.id}
onClick={() => {
onSelect({
id: creative.id,
name: creative.name,
thumbnail_url: creative.media_items?.[0]?.s3_url,
});
onOpenChange(false);
}}
/>
))}
</div>
)}
{currentCreativeId && onClear && (
<div className="shrink-0 pt-4 border-t mt-4">
<Button
variant="outline"
className="w-full"
onClick={() => {
onClear();
onOpenChange(false);
}}
>
Отменить выбор креатива
</Button>
</div>
)}
</DialogContent>
</Dialog>
);
}