Fix email template preview UX and debug template loading

- Convert EmailTemplatePreview from bottom card to modal Dialog component
- Replace problematic bottom preview with clean modal popup
- Add proper modal state management (open/close handlers)
- Debug template loading with comprehensive error handling and logging
- Add user feedback for template seeding and loading errors
- Improve fetchData() and seedTemplates() with try-catch blocks
- Add console logging for debugging template initialization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
dwindown
2025-12-22 20:57:33 +07:00
parent f743a79674
commit 1982033ac4
2 changed files with 242 additions and 142 deletions

View File

@@ -3,8 +3,16 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Alert, AlertDescription } from '@/components/ui/alert'; import { Alert, AlertDescription } from '@/components/ui/alert';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from '@/components/ui/dialog';
import { toast } from '@/hooks/use-toast'; import { toast } from '@/hooks/use-toast';
import { Eye, Send, Mail } from 'lucide-react'; import { Eye, Send, Mail, X } from 'lucide-react';
import { EmailTemplateRenderer, ShortcodeProcessor } from '@/lib/email-templates/master-template'; import { EmailTemplateRenderer, ShortcodeProcessor } from '@/lib/email-templates/master-template';
interface NotificationTemplate { interface NotificationTemplate {
@@ -22,9 +30,17 @@ interface EmailTemplatePreviewProps {
template: NotificationTemplate; template: NotificationTemplate;
onTest?: (template: NotificationTemplate) => void; onTest?: (template: NotificationTemplate) => void;
isTestSending?: boolean; isTestSending?: boolean;
open: boolean;
onClose: () => void;
} }
export function EmailTemplatePreview({ template, onTest, isTestSending = false }: EmailTemplatePreviewProps) { export function EmailTemplatePreview({
template,
onTest,
isTestSending = false,
open,
onClose
}: EmailTemplatePreviewProps) {
const [previewMode, setPreviewMode] = useState<'master' | 'content'>('master'); const [previewMode, setPreviewMode] = useState<'master' | 'content'>('master');
const [testEmail, setTestEmail] = useState(''); const [testEmail, setTestEmail] = useState('');
const [showTestForm, setShowTestForm] = useState(false); const [showTestForm, setShowTestForm] = useState(false);
@@ -60,6 +76,25 @@ export function EmailTemplatePreview({ template, onTest, isTestSending = false }
const previewHtml = generatePreview(); const previewHtml = generatePreview();
return ( return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Mail className="w-5 h-5" />
Preview: {template.name}
</DialogTitle>
<DialogDescription>
Preview template email dengan master styling
</DialogDescription>
<Button
variant="ghost"
size="icon"
onClick={onClose}
>
<X className="w-4 h-4" />
</Button>
</DialogHeader>
<div className="space-y-4"> <div className="space-y-4">
{/* Preview Controls */} {/* Preview Controls */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@@ -134,7 +169,7 @@ export function EmailTemplatePreview({ template, onTest, isTestSending = false }
{previewMode === 'master' ? 'Full Email Preview' : 'Content Preview'} {previewMode === 'master' ? 'Full Email Preview' : 'Content Preview'}
</span> </span>
</div> </div>
<div className="bg-white" style={{ height: '600px', overflow: 'hidden' }}> <div className="bg-white" style={{ height: '500px', overflow: 'hidden' }}>
<iframe <iframe
srcDoc={previewHtml} srcDoc={previewHtml}
className="w-full h-full border-0" className="w-full h-full border-0"
@@ -163,6 +198,32 @@ export function EmailTemplatePreview({ template, onTest, isTestSending = false }
))} ))}
</div> </div>
</div> </div>
{/* Template Actions */}
<DialogFooter>
<Button variant="outline" onClick={onClose}>
Close
</Button>
{!showTestForm && (
<Button
onClick={() => setShowTestForm(true)}
className="flex-1"
>
<Send className="w-4 h-4 mr-2" />
Test Email
</Button>
)}
{showTestForm && (
<Button
onClick={() => setShowTestForm(false)}
variant="outline"
>
Cancel
</Button>
)}
</DialogFooter>
</div> </div>
</DialogContent>
</Dialog>
); );
} }

View File

@@ -346,26 +346,55 @@ export function NotifikasiTab() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [expandedTemplates, setExpandedTemplates] = useState<Set<string>>(new Set()); const [expandedTemplates, setExpandedTemplates] = useState<Set<string>>(new Set());
const [testingTemplate, setTestingTemplate] = useState<string | null>(null); const [testingTemplate, setTestingTemplate] = useState<string | null>(null);
const [selectedTemplate, setSelectedTemplate] = useState<NotificationTemplate | null>(null); const [previewTemplate, setPreviewTemplate] = useState<NotificationTemplate | null>(null);
const [previewMode, setPreviewMode] = useState<'master' | 'content'>('master'); const [isPreviewOpen, setIsPreviewOpen] = useState(false);
useEffect(() => { useEffect(() => {
fetchData(); fetchData();
}, []); }, []);
const fetchData = async () => { const fetchData = async () => {
try {
console.log('Fetching templates...');
// Fetch templates // Fetch templates
const { data: templatesData } = await supabase.from('notification_templates').select('*').order('key'); const { data: templatesData, error: fetchError } = await supabase.from('notification_templates').select('*').order('key');
if (fetchError) {
console.error('Error fetching templates:', fetchError);
toast({
title: 'Error',
description: 'Gagal mengambil template: ' + fetchError.message,
variant: 'destructive'
});
setLoading(false);
return;
}
console.log('Templates data:', templatesData);
if (templatesData && templatesData.length > 0) { if (templatesData && templatesData.length > 0) {
console.log('Setting templates from database:', templatesData.length);
setTemplates(templatesData); setTemplates(templatesData);
} else { } else {
console.log('No templates found, seeding default templates...');
// Seed default templates if none exist // Seed default templates if none exist
await seedTemplates(); await seedTemplates();
} }
} catch (error) {
console.error('Unexpected error in fetchData:', error);
toast({
title: 'Error',
description: 'Terjadi kesalahan tak terduga saat mengambil data',
variant: 'destructive'
});
} finally {
setLoading(false); setLoading(false);
}
}; };
const seedTemplates = async () => { const seedTemplates = async () => {
try {
console.log('Seeding default templates...');
const toInsert = DEFAULT_TEMPLATES.map(t => ({ const toInsert = DEFAULT_TEMPLATES.map(t => ({
key: t.key, key: t.key,
name: t.name, name: t.name,
@@ -374,8 +403,36 @@ export function NotifikasiTab() {
email_body_html: t.defaultBody, email_body_html: t.defaultBody,
webhook_url: '', webhook_url: '',
})); }));
console.log('Inserting templates:', toInsert.length);
const { data, error } = await supabase.from('notification_templates').insert(toInsert).select(); const { data, error } = await supabase.from('notification_templates').insert(toInsert).select();
if (!error && data) setTemplates(data);
if (error) {
console.error('Error seeding templates:', error);
toast({
title: 'Error',
description: 'Gagal membuat template default: ' + error.message,
variant: 'destructive'
});
return;
}
console.log('Templates seeded successfully:', data);
if (data) {
setTemplates(data);
toast({
title: 'Berhasil',
description: `Berhasil membuat ${data.length} template default`
});
}
} catch (error) {
console.error('Unexpected error in seedTemplates:', error);
toast({
title: 'Error',
description: 'Terjadi kesalahan saat membuat template default',
variant: 'destructive'
});
}
}; };
@@ -587,7 +644,8 @@ export function NotifikasiTab() {
<Button <Button
onClick={() => { onClick={() => {
updateTemplate(template); updateTemplate(template);
setSelectedTemplate(template); setPreviewTemplate(template);
setIsPreviewOpen(true);
}} }}
className="shadow-sm flex-1" className="shadow-sm flex-1"
> >
@@ -620,33 +678,14 @@ export function NotifikasiTab() {
</CardContent> </CardContent>
</Card> </Card>
{/* Consolidated Email Preview */} {/* Modal Email Preview */}
{selectedTemplate && (
<Card className="border-2 border-border">
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span>Preview: {selectedTemplate.name}</span>
<Button
variant="outline"
size="sm"
onClick={() => setSelectedTemplate(null)}
>
Tutup Preview
</Button>
</CardTitle>
<CardDescription>
Preview template email dengan master styling
</CardDescription>
</CardHeader>
<CardContent>
<EmailTemplatePreview <EmailTemplatePreview
template={selectedTemplate} template={previewTemplate!}
open={isPreviewOpen}
onClose={() => setIsPreviewOpen(false)}
onTest={sendTestEmail} onTest={sendTestEmail}
isTestSending={testingTemplate === selectedTemplate.id} isTestSending={testingTemplate === previewTemplate?.id}
/> />
</CardContent>
</Card>
)}
</div> </div>
); );
} }