import React, { useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Mail, Plus, Search, Filter, Edit2, Trash2, Copy, Eye, X, Calendar, Bell, CheckCircle, Megaphone, FileText, BarChart3, Package, AlertTriangle, Sparkles, Smile, Minus, Grid3x3, List, Check, Square, CheckSquare } from 'lucide-react'; import api from '../api/client'; import { EmailTemplate, EmailTemplateCategory } from '../types'; import EmailTemplateForm from '../components/EmailTemplateForm'; // Category icon mapping const categoryIcons: Record = { APPOINTMENT: , REMINDER: , CONFIRMATION: , MARKETING: , NOTIFICATION: , REPORT: , OTHER: , }; // Category colors const categoryColors: Record = { APPOINTMENT: 'bg-indigo-50 text-indigo-700 ring-indigo-700/20 dark:bg-indigo-400/10 dark:text-indigo-400 ring-indigo-400/30', REMINDER: 'bg-orange-50 text-orange-700 ring-orange-700/20 dark:bg-orange-400/10 dark:text-orange-400 ring-orange-400/30', CONFIRMATION: 'bg-green-50 text-green-700 ring-green-700/20 dark:bg-green-400/10 dark:text-green-400 ring-green-400/30', MARKETING: 'bg-purple-50 text-purple-700 ring-purple-700/20 dark:bg-purple-400/10 dark:text-purple-400 ring-purple-400/30', NOTIFICATION: 'bg-sky-50 text-sky-700 ring-sky-700/20 dark:bg-sky-400/10 dark:text-sky-400 ring-sky-400/30', REPORT: 'bg-rose-50 text-rose-700 ring-rose-700/20 dark:bg-rose-400/10 dark:text-rose-400 ring-rose-400/30', OTHER: 'bg-gray-50 text-gray-700 ring-gray-700/20 dark:bg-gray-400/10 dark:text-gray-400 ring-gray-400/30', }; interface TemplatePreset { name: string; description: string; style: string; subject: string; html_content: string; text_content: string; } const styleIcons: Record = { professional: , friendly: , minimalist: , }; const styleColors: Record = { professional: 'bg-purple-50 text-purple-700 ring-purple-700/20 dark:bg-purple-400/10 dark:text-purple-400 ring-purple-400/30', friendly: 'bg-green-50 text-green-700 ring-green-700/20 dark:bg-green-400/10 dark:text-green-400 ring-green-400/30', minimalist: 'bg-gray-50 text-gray-700 ring-gray-700/20 dark:bg-gray-400/10 dark:text-gray-400 ring-gray-400/30', }; const EmailTemplates: React.FC = () => { const { t } = useTranslation(); const queryClient = useQueryClient(); const [activeView, setActiveView] = useState<'my-templates' | 'browse'>('browse'); const [searchQuery, setSearchQuery] = useState(''); const [selectedCategory, setSelectedCategory] = useState('ALL'); const [selectedStyle, setSelectedStyle] = useState('all'); const [showCreateModal, setShowCreateModal] = useState(false); const [editingTemplate, setEditingTemplate] = useState(null); const [showDeleteModal, setShowDeleteModal] = useState(false); const [templateToDelete, setTemplateToDelete] = useState(null); const [showPreviewModal, setShowPreviewModal] = useState(false); const [previewTemplate, setPreviewTemplate] = useState(null); const [previewPreset, setPreviewPreset] = useState(null); const [selectedTemplates, setSelectedTemplates] = useState>(new Set()); const [showBulkDeleteModal, setShowBulkDeleteModal] = useState(false); // Fetch email templates const { data: templates = [], isLoading, error } = useQuery({ queryKey: ['email-templates'], queryFn: async () => { const { data } = await api.get('/email-templates/'); return data.map((t: any) => ({ id: String(t.id), name: t.name, description: t.description, subject: t.subject, htmlContent: t.html_content, textContent: t.text_content, scope: t.scope, isDefault: t.is_default, category: t.category, previewContext: t.preview_context, createdBy: t.created_by, createdByName: t.created_by_name, createdAt: t.created_at, updatedAt: t.updated_at, })); }, }); // Fetch template presets const { data: presetsData, isLoading: presetsLoading } = useQuery<{ presets: Record }>({ queryKey: ['email-template-presets'], queryFn: async () => { const { data } = await api.get('/email-templates/presets/'); return data; }, }); // Delete template mutation const deleteMutation = useMutation({ mutationFn: async (templateId: string) => { await api.delete(`/email-templates/${templateId}/`); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['email-templates'] }); setShowDeleteModal(false); setTemplateToDelete(null); }, }); // Duplicate template mutation const duplicateMutation = useMutation({ mutationFn: async (templateId: string) => { const { data } = await api.post(`/email-templates/${templateId}/duplicate/`); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['email-templates'] }); }, }); // Bulk delete mutation const bulkDeleteMutation = useMutation({ mutationFn: async (templateIds: string[]) => { // Delete templates one by one (backend may not support bulk delete) await Promise.all(templateIds.map(id => api.delete(`/email-templates/${id}/`))); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['email-templates'] }); setSelectedTemplates(new Set()); setShowBulkDeleteModal(false); }, }); // Filter templates const filteredTemplates = useMemo(() => { let result = templates; // Filter by category if (selectedCategory !== 'ALL') { result = result.filter(t => t.category === selectedCategory); } // Filter by search query if (searchQuery.trim()) { const query = searchQuery.toLowerCase(); result = result.filter(t => t.name.toLowerCase().includes(query) || t.description.toLowerCase().includes(query) || t.subject.toLowerCase().includes(query) ); } return result; }, [templates, selectedCategory, searchQuery]); // Filter presets const filteredPresets = useMemo(() => { if (!presetsData?.presets) return []; let allPresets: (TemplatePreset & { category: EmailTemplateCategory })[] = []; // Flatten presets from all categories Object.entries(presetsData.presets).forEach(([category, presets]) => { allPresets.push(...presets.map(p => ({ ...p, category: category as EmailTemplateCategory }))); }); // Filter by category if (selectedCategory !== 'ALL') { allPresets = allPresets.filter(p => p.category === selectedCategory); } // Filter by style if (selectedStyle !== 'all') { allPresets = allPresets.filter(p => p.style === selectedStyle); } // Filter by search query if (searchQuery.trim()) { const query = searchQuery.toLowerCase(); allPresets = allPresets.filter(p => p.name.toLowerCase().includes(query) || p.description.toLowerCase().includes(query) || p.subject.toLowerCase().includes(query) ); } return allPresets; }, [presetsData, selectedCategory, selectedStyle, searchQuery]); // Get available styles from all presets const availableStyles = useMemo(() => { if (!presetsData?.presets) return []; const allPresets = Object.values(presetsData.presets).flat(); return Array.from(new Set(allPresets.map(p => p.style))); }, [presetsData]); const handleEdit = (template: EmailTemplate) => { setEditingTemplate(template); setShowCreateModal(true); }; const handleDelete = (template: EmailTemplate) => { setTemplateToDelete(template); setShowDeleteModal(true); }; const handleDuplicate = (template: EmailTemplate) => { duplicateMutation.mutate(template.id); }; const handlePreview = (template: EmailTemplate) => { setPreviewTemplate(template); setShowPreviewModal(true); }; const handleFormClose = () => { setShowCreateModal(false); setEditingTemplate(null); }; const handleFormSuccess = () => { queryClient.invalidateQueries({ queryKey: ['email-templates'] }); handleFormClose(); }; // Selection handlers const handleSelectTemplate = (templateId: string) => { setSelectedTemplates(prev => { const next = new Set(prev); if (next.has(templateId)) { next.delete(templateId); } else { next.add(templateId); } return next; }); }; const handleSelectAll = () => { if (selectedTemplates.size === filteredTemplates.length) { // Deselect all setSelectedTemplates(new Set()); } else { // Select all filtered templates setSelectedTemplates(new Set(filteredTemplates.map(t => t.id))); } }; const handleBulkDelete = () => { setShowBulkDeleteModal(true); }; const confirmBulkDelete = () => { bulkDeleteMutation.mutate(Array.from(selectedTemplates)); }; const handleUsePreset = (preset: TemplatePreset & { category: EmailTemplateCategory }) => { // Create a new template from the preset setEditingTemplate({ id: '', name: preset.name, description: preset.description, subject: preset.subject, htmlContent: preset.html_content, textContent: preset.text_content, category: preset.category, scope: 'BUSINESS', isDefault: false, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), } as EmailTemplate); setShowCreateModal(true); }; if (isLoading) { return (
); } if (error) { return (

{t('common.error')}: {error instanceof Error ? error.message : 'Unknown error'}

); } return (
{/* Header */}

{t('emailTemplates.title', 'Email Templates')}

{t('emailTemplates.description', 'Browse professional templates or create your own')}

{/* View Tabs */}
{/* Search and Filters */}
{/* Search Bar */}
setSearchQuery(e.target.value)} placeholder={t('emailTemplates.search', 'Search templates...')} className="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-500 focus:border-brand-500" />
{/* Category Filter */}
{/* Style Filter (Browse Mode Only) */} {activeView === 'browse' && availableStyles.length > 0 && (
{availableStyles.map(style => ( ))}
)}
{/* Active Filters Summary */} {(searchQuery || selectedCategory !== 'ALL' || selectedStyle !== 'all') && (
{t('emailTemplates.showing', 'Showing')} {activeView === 'browse' ? filteredPresets.length : filteredTemplates.length} {t('emailTemplates.results', 'results')}
)}
{/* Browse Templates View */} {activeView === 'browse' && ( presetsLoading ? (
) : filteredPresets.length === 0 ? (

{searchQuery || selectedCategory !== 'ALL' || selectedStyle !== 'all' ? t('emailTemplates.noPresets', 'No templates found matching your criteria') : t('emailTemplates.noPresetsAvailable', 'No templates available')}

) : (
{filteredPresets.map((preset, index) => (
{/* Preview */}