import { useState, useRef, useMemo } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' import { Link } from 'react-router-dom' import { adminBroadcastsApi, Broadcast, BroadcastFilter, TariffFilter, BroadcastCreateRequest, } from '../api/adminBroadcasts' // Icons const BackIcon = () => ( ) const BroadcastIcon = () => ( ) const PlusIcon = () => ( ) const XIcon = () => ( ) const RefreshIcon = () => ( ) const StopIcon = () => ( ) const PhotoIcon = () => ( ) const VideoIcon = () => ( ) const DocumentIcon = () => ( ) const UsersIcon = () => ( ) const ChevronDownIcon = () => ( ) // Status badge component function StatusBadge({ status }: { status: string }) { const statusConfig: Record = { queued: { bg: 'bg-yellow-500/20', text: 'text-yellow-400', label: 'В очереди' }, in_progress: { bg: 'bg-blue-500/20', text: 'text-blue-400', label: 'Отправляется' }, completed: { bg: 'bg-green-500/20', text: 'text-green-400', label: 'Завершено' }, partial: { bg: 'bg-orange-500/20', text: 'text-orange-400', label: 'Частично' }, failed: { bg: 'bg-red-500/20', text: 'text-red-400', label: 'Ошибка' }, cancelled: { bg: 'bg-gray-500/20', text: 'text-gray-400', label: 'Отменено' }, cancelling: { bg: 'bg-yellow-500/20', text: 'text-yellow-400', label: 'Отменяется' }, } const config = statusConfig[status] || statusConfig.queued return ( {config.label} ) } // Filter labels const FILTER_GROUP_LABELS: Record = { basic: 'Основные', subscription: 'По подписке', traffic: 'По трафику', registration: 'По регистрации', activity: 'По активности', source: 'По источнику', tariff: 'По тарифу', } // Create broadcast modal interface CreateModalProps { onClose: () => void onSuccess: () => void } function CreateBroadcastModal({ onClose, onSuccess }: CreateModalProps) { const { t } = useTranslation() const queryClient = useQueryClient() const fileInputRef = useRef(null) const [target, setTarget] = useState('') const [messageText, setMessageText] = useState('') const [selectedButtons, setSelectedButtons] = useState(['home']) const [mediaFile, setMediaFile] = useState(null) const [mediaType, setMediaType] = useState<'photo' | 'video' | 'document'>('photo') const [mediaPreview, setMediaPreview] = useState(null) const [uploadedFileId, setUploadedFileId] = useState(null) const [isUploading, setIsUploading] = useState(false) const [showFilters, setShowFilters] = useState(false) // Fetch filters const { data: filtersData, isLoading: filtersLoading } = useQuery({ queryKey: ['admin', 'broadcasts', 'filters'], queryFn: adminBroadcastsApi.getFilters, }) // Fetch buttons const { data: buttonsData } = useQuery({ queryKey: ['admin', 'broadcasts', 'buttons'], queryFn: adminBroadcastsApi.getButtons, }) // Preview mutation const previewMutation = useMutation({ mutationFn: adminBroadcastsApi.preview, }) // Create mutation const createMutation = useMutation({ mutationFn: adminBroadcastsApi.create, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin', 'broadcasts'] }) onSuccess() onClose() }, }) // Group filters const groupedFilters = useMemo(() => { if (!filtersData) return {} const groups: Record = {} // Basic filters filtersData.filters.forEach(f => { const group = f.group || 'basic' if (!groups[group]) groups[group] = [] groups[group].push(f) }) // Tariff filters if (filtersData.tariff_filters.length > 0) { groups['tariff'] = filtersData.tariff_filters } // Custom filters filtersData.custom_filters.forEach(f => { const group = f.group || 'custom' if (!groups[group]) groups[group] = [] groups[group].push(f) }) return groups }, [filtersData]) // Selected filter info const selectedFilter = useMemo(() => { if (!target || !filtersData) return null const all = [ ...filtersData.filters, ...filtersData.tariff_filters, ...filtersData.custom_filters, ] return all.find(f => f.key === target) }, [target, filtersData]) // Handle file selection const handleFileSelect = async (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return setMediaFile(file) // Determine media type if (file.type.startsWith('image/')) { setMediaType('photo') setMediaPreview(URL.createObjectURL(file)) } else if (file.type.startsWith('video/')) { setMediaType('video') setMediaPreview(null) } else { setMediaType('document') setMediaPreview(null) } // Upload file setIsUploading(true) try { const result = await adminBroadcastsApi.uploadMedia(file, mediaType) setUploadedFileId(result.file_id) } catch (err) { console.error('Upload failed:', err) setMediaFile(null) setMediaPreview(null) } finally { setIsUploading(false) } } // Remove media const handleRemoveMedia = () => { setMediaFile(null) setMediaPreview(null) setUploadedFileId(null) if (fileInputRef.current) { fileInputRef.current.value = '' } } // Toggle button const toggleButton = (key: string) => { setSelectedButtons(prev => prev.includes(key) ? prev.filter(b => b !== key) : [...prev, key] ) } // Submit const handleSubmit = () => { if (!target || !messageText.trim()) return const data: BroadcastCreateRequest = { target, message_text: messageText, selected_buttons: selectedButtons, } if (uploadedFileId) { data.media = { type: mediaType, file_id: uploadedFileId, } } createMutation.mutate(data) } const recipientsCount = previewMutation.data?.count ?? selectedFilter?.count ?? null return (
{/* Header */}

{t('admin.broadcasts.create')}

{/* Content */}
{/* Filter selection */}
{showFilters && (
{filtersLoading ? (
Loading...
) : ( Object.entries(groupedFilters).map(([group, filters]) => (
{FILTER_GROUP_LABELS[group] || group}
{filters.map(filter => ( ))}
)) )}
)}
{/* Message text */}