import { useState, useRef, useMemo } from 'react'; import { useNavigate } from 'react-router'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { adminBroadcastsApi, BroadcastFilter, TariffFilter, BroadcastChannel, CombinedBroadcastCreateRequest, } from '../api/adminBroadcasts'; import { AdminBackButton } from '../components/admin'; // Icons const BroadcastIcon = () => ( ); const XIcon = () => ( ); const RefreshIcon = () => ( ); const PhotoIcon = () => ( ); const VideoIcon = () => ( ); const DocumentIcon = () => ( ); const UsersIcon = () => ( ); const ChevronDownIcon = () => ( ); const TelegramIcon = () => ( ); const EmailIcon = () => ( ); // Filter labels 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', email: 'admin.broadcasts.filterGroups.email', }; export default function AdminBroadcastCreate() { const { t } = useTranslation(); const navigate = useNavigate(); const queryClient = useQueryClient(); const fileInputRef = useRef(null); // Channel selection const [channel, setChannel] = useState('telegram'); // Common state const [target, setTarget] = useState(''); const [showFilters, setShowFilters] = useState(false); // Telegram-specific state 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); // Email-specific state const [emailSubject, setEmailSubject] = useState(''); const [emailContent, setEmailContent] = useState(''); // Fetch Telegram filters const { data: filtersData, isLoading: filtersLoading } = useQuery({ queryKey: ['admin', 'broadcasts', 'filters'], queryFn: adminBroadcastsApi.getFilters, enabled: channel === 'telegram', }); // Fetch Email filters const { data: emailFiltersData, isLoading: emailFiltersLoading } = useQuery({ queryKey: ['admin', 'broadcasts', 'email-filters'], queryFn: adminBroadcastsApi.getEmailFilters, enabled: channel === 'email', }); // Fetch buttons const { data: buttonsData } = useQuery({ queryKey: ['admin', 'broadcasts', 'buttons'], queryFn: adminBroadcastsApi.getButtons, enabled: channel === 'telegram', }); // Preview mutations const previewMutation = useMutation({ mutationFn: adminBroadcastsApi.preview, }); const emailPreviewMutation = useMutation({ mutationFn: adminBroadcastsApi.previewEmail, }); // Create mutation const createMutation = useMutation({ mutationFn: adminBroadcastsApi.createCombined, onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ['admin', 'broadcasts'] }); navigate(`/admin/broadcasts/${data.id}`); }, }); // Group Telegram filters const groupedFilters = useMemo(() => { if (!filtersData) return {}; const groups: Record = {}; filtersData.filters.forEach((f) => { const group = f.group || 'basic'; if (!groups[group]) groups[group] = []; groups[group].push(f); }); if (filtersData.tariff_filters.length > 0) { groups['tariff'] = filtersData.tariff_filters; } filtersData.custom_filters.forEach((f) => { const group = f.group || 'custom'; if (!groups[group]) groups[group] = []; groups[group].push(f); }); return groups; }, [filtersData]); // Group Email filters const groupedEmailFilters = useMemo(() => { if (!emailFiltersData) return {}; const groups: Record = {}; emailFiltersData.filters.forEach((f) => { const group = f.group || 'email'; if (!groups[group]) groups[group] = []; groups[group].push(f); }); return groups; }, [emailFiltersData]); // Current filters based on channel const currentFilters = channel === 'telegram' ? groupedFilters : groupedEmailFilters; const isFiltersLoading = channel === 'telegram' ? filtersLoading : emailFiltersLoading; // Selected filter info const selectedFilter = useMemo(() => { if (!target) return null; if (channel === 'telegram' && filtersData) { const all = [ ...filtersData.filters, ...filtersData.tariff_filters, ...filtersData.custom_filters, ]; return all.find((f) => f.key === target); } if (channel === 'email' && emailFiltersData) { return emailFiltersData.filters.find((f) => f.key === target); } return null; }, [target, channel, filtersData, emailFiltersData]); // Handle channel change const handleChannelChange = (newChannel: BroadcastChannel) => { setChannel(newChannel); setTarget(''); previewMutation.reset(); emailPreviewMutation.reset(); }; // Handle filter selection const handleFilterSelect = (filterKey: string) => { setTarget(filterKey); setShowFilters(false); if (channel === 'telegram') { previewMutation.mutate(filterKey); } else { emailPreviewMutation.mutate(filterKey); } }; // Handle file selection const handleFileSelect = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setMediaFile(file); 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); } 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], ); }; // Validate form const isValid = useMemo(() => { if (!target) return false; if (channel === 'telegram') { return messageText.trim().length > 0; } if (channel === 'email') { return emailSubject.trim().length > 0 && emailContent.trim().length > 0; } return false; }, [target, channel, messageText, emailSubject, emailContent]); // Submit const handleSubmit = () => { if (!isValid) return; const data: CombinedBroadcastCreateRequest = { channel, target, }; if (channel === 'telegram') { data.message_text = messageText; data.selected_buttons = selectedButtons; if (uploadedFileId) { data.media = { type: mediaType, file_id: uploadedFileId, }; } } if (channel === 'email') { data.email_subject = emailSubject; data.email_html_content = emailContent; } createMutation.mutate(data); }; const recipientsCount = channel === 'telegram' ? (previewMutation.data?.count ?? selectedFilter?.count ?? null) : (emailPreviewMutation.data?.count ?? selectedFilter?.count ?? null); return ( {/* Header */} {t('admin.broadcasts.create')} {t('admin.broadcasts.subtitle')} {/* Channel selection */} {t('admin.broadcasts.selectChannel')} handleChannelChange('telegram')} className={`flex flex-1 items-center justify-center gap-2 rounded-lg border p-4 transition-all ${ channel === 'telegram' ? 'border-accent-500 bg-accent-500/10 text-accent-400' : 'border-dark-700 bg-dark-800 text-dark-300 hover:border-dark-600' }`} > {t('admin.broadcasts.channel.telegram')} handleChannelChange('email')} className={`flex flex-1 items-center justify-center gap-2 rounded-lg border p-4 transition-all ${ channel === 'email' ? 'border-accent-500 bg-accent-500/10 text-accent-400' : 'border-dark-700 bg-dark-800 text-dark-300 hover:border-dark-600' }`} > {t('admin.broadcasts.channel.email')} {/* Content */} {/* Section title */} {channel === 'telegram' ? t('admin.broadcasts.telegramSection') : t('admin.broadcasts.emailSection')} {/* Filter selection */} {channel === 'telegram' ? t('admin.broadcasts.selectFilter') : t('admin.broadcasts.selectEmailFilter')} setShowFilters(!showFilters)} className="flex w-full items-center justify-between rounded-lg border border-dark-700 bg-dark-800 p-3 text-left transition-colors hover:border-dark-600" > {selectedFilter ? selectedFilter.label : channel === 'telegram' ? t('admin.broadcasts.selectFilterPlaceholder') : t('admin.broadcasts.selectEmailFilterPlaceholder')} {recipientsCount !== null && ( {recipientsCount} {t('admin.broadcasts.recipients')} )} {showFilters && ( {isFiltersLoading ? ( {t('common.loading')} ) : ( Object.entries(currentFilters).map(([group, filters]) => ( {FILTER_GROUP_LABEL_KEYS[group] ? t(FILTER_GROUP_LABEL_KEYS[group]) : group} {filters.map((filter) => ( handleFilterSelect(filter.key)} className={`flex w-full items-center justify-between px-3 py-2 text-left transition-colors hover:bg-dark-700 ${ target === filter.key ? 'bg-accent-500/20' : '' }`} > {filter.label} {filter.count !== null && filter.count !== undefined && ( {filter.count} )} ))} )) )} )} {/* Telegram-specific fields */} {channel === 'telegram' && ( <> {/* Message text */} {t('admin.broadcasts.messageText')} setMessageText(e.target.value)} placeholder={t('admin.broadcasts.messageTextPlaceholder')} rows={6} maxLength={4000} className="input min-h-[150px] resize-y" /> {messageText.length}/4000 {/* Media upload */} {t('admin.broadcasts.media')} {mediaFile ? ( {mediaType === 'photo' && } {mediaType === 'video' && } {mediaType === 'document' && } {mediaFile.name} {(mediaFile.size / 1024 / 1024).toFixed(2)} MB {mediaPreview && ( )} {isUploading && ( {t('admin.broadcasts.uploading')} )} ) : ( fileInputRef.current?.click()} className="flex w-full items-center justify-center gap-2 rounded-lg border border-dashed border-dark-600 bg-dark-800/50 p-6 text-dark-400 transition-colors hover:border-dark-500 hover:bg-dark-800 hover:text-dark-300" > {t('admin.broadcasts.addMedia')} )} {/* Buttons selection */} {t('admin.broadcasts.buttons')} {buttonsData?.buttons.map((button) => ( toggleButton(button.key)} className={`rounded-lg px-3 py-2 text-sm transition-colors ${ selectedButtons.includes(button.key) ? 'bg-accent-500 text-white' : 'border border-dark-700 bg-dark-800 text-dark-300 hover:bg-dark-700' }`} > {button.label} ))} > )} {/* Email-specific fields */} {channel === 'email' && ( <> {/* Email subject */} {t('admin.broadcasts.emailSubject')} setEmailSubject(e.target.value)} placeholder={t('admin.broadcasts.emailSubjectPlaceholder')} className="input" maxLength={200} /> {/* Email content */} {t('admin.broadcasts.emailContent')} {t('admin.broadcasts.emailContentHint')} setEmailContent(e.target.value)} placeholder={t('admin.broadcasts.emailContentPlaceholder')} rows={10} className="input min-h-[200px] resize-y font-mono text-sm" /> {/* Email variables hint */} {t('admin.broadcasts.emailVariables')} {['{{user_name}}', '{{email}}', '{{user_id}}'].map((variable) => ( {variable} ))} > )} {/* Footer */} {recipientsCount !== null && ( {t('admin.broadcasts.willBeSent')}:{' '} {recipientsCount}{' '} {t('admin.broadcasts.users')} )} navigate('/admin/broadcasts')} className="btn-secondary"> {t('common.cancel')} {createMutation.isPending ? : } {t('admin.broadcasts.send')} ); }
{t('admin.broadcasts.subtitle')}
{mediaFile.name}
{(mediaFile.size / 1024 / 1024).toFixed(2)} MB
{t('admin.broadcasts.emailContentHint')}
{t('admin.broadcasts.emailVariables')}
{variable}