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 const statusConfig: Record = { queued: { bg: 'bg-yellow-500/20', text: 'text-yellow-400', labelKey: 'admin.broadcasts.status.queued', }, in_progress: { bg: 'bg-blue-500/20', text: 'text-blue-400', labelKey: 'admin.broadcasts.status.inProgress', }, completed: { bg: 'bg-green-500/20', text: 'text-green-400', labelKey: 'admin.broadcasts.status.completed', }, partial: { bg: 'bg-orange-500/20', text: 'text-orange-400', labelKey: 'admin.broadcasts.status.partial', }, failed: { bg: 'bg-red-500/20', text: 'text-red-400', labelKey: 'admin.broadcasts.status.failed' }, cancelled: { bg: 'bg-gray-500/20', text: 'text-gray-400', labelKey: 'admin.broadcasts.status.cancelled', }, cancelling: { bg: 'bg-yellow-500/20', text: 'text-yellow-400', labelKey: 'admin.broadcasts.status.cancelling', }, }; function StatusBadge({ status }: { status: string }) { const { t } = useTranslation(); const config = statusConfig[status] || statusConfig.queued; return ( {t(config.labelKey)} ); } // Filter labels (labelKey pattern: store i18n keys, resolve at render time with t()) const FILTER_GROUP_LABEL_KEYS: Record = { basic: 'admin.broadcasts.filterGroups.basic', subscription: 'admin.broadcasts.filterGroups.subscription', traffic: 'admin.broadcasts.filterGroups.traffic', registration: 'admin.broadcasts.filterGroups.registration', activity: 'admin.broadcasts.filterGroups.activity', source: 'admin.broadcasts.filterGroups.source', tariff: 'admin.broadcasts.filterGroups.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_LABEL_KEYS[group] ? t(FILTER_GROUP_LABEL_KEYS[group]) : group}
{filters.map((filter) => ( ))}
)) )}
)}
{/* Message text */}