PozoAppOptimandSEO/src/AdminPanel/AdminPanelForms/WebinarSubmissionsViewer.jsx

515 lines
18 KiB
React
Raw Normal View History

import React, { useState, useEffect } from 'react';
import { Table, Tabs, Tag, Button, Input, DatePicker, Space, message, Modal, Descriptions } from 'antd';
import { SearchOutlined, DownloadOutlined, ReloadOutlined, TeamOutlined, PlayCircleOutlined, CalendarOutlined } from '@ant-design/icons';
import { useDispatch } from 'react-redux';
import { getAdminPanel } from '../../features/AdminPanel/AdminPanel';
import '../Styles/WebinarSubmissionsViewer.scss';
const { TabPane } = Tabs;
const { RangePicker } = DatePicker;
const WebinarSubmissionsViewer = () => {
const dispatch = useDispatch();
const [registrations, setRegistrations] = useState([]);
const [recordingAccess, setRecordingAccess] = useState([]);
const [loading, setLoading] = useState(false);
const [searchText, setSearchText] = useState('');
const [isModalVisible, setIsModalVisible] = useState(false);
const [selectedRecord, setSelectedRecord] = useState(null);
useEffect(() => {
loadData();
}, [dispatch]);
const loadData = async () => {
setLoading(true);
try {
const response = await dispatch(getAdminPanel()).unwrap();
// Filter registrations
const registrationData = response.filter(item =>
item.SectionName && item.SectionName.startsWith('WebinarRegistrations_')
);
// Filter recording access
const recordingData = response.filter(item =>
item.SectionName && item.SectionName.startsWith('WebinarRecordingAccess_')
);
// Transform registrations
const transformedRegistrations = registrationData.map((item, index) => {
let content = {};
try {
const contentStr = item.Content || item.SectionContent || '{}';
content = JSON.parse(contentStr);
} catch (e) {
console.error('Error parsing content:', e);
}
return {
key: item.SectionId || index,
id: item.SectionId,
sectionName: item.SectionName,
sectionHdr: item.SectionHdr,
sectionDesc: item.SectionDesc,
createdAt: item.CreatedDate || new Date().toISOString(),
...content,
rawData: item
};
});
// Transform recording access
const transformedRecording = recordingData.map((item, index) => {
let content = {};
try {
const contentString = item.Content || item.SectionContent || '{}';
content = JSON.parse(contentString);
} catch (e) {
console.error('Error parsing content:', e);
}
return {
key: item.SectionId || index,
id: item.SectionId,
...content,
createdAt: item.CreatedDate,
rawData: item
};
});
setRegistrations(transformedRegistrations);
setRecordingAccess(transformedRecording);
setLoading(false);
} catch (error) {
console.error('Error loading data:', error);
message.error('Failed to load webinar submissions');
setLoading(false);
}
};
const handleRefresh = () => {
loadData();
message.success('Data refreshed successfully');
};
// Registration columns
const registrationColumns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 60,
},
{
title: 'Submitted By',
dataIndex: 'sectionDesc',
key: 'sectionDesc',
width: 200,
render: (text) => {
// Extract name from "Registration from [Name]"
const match = text?.match(/Registration from (.+)/);
return match ? match[1] : text || '-';
},
},
{
title: 'Type',
dataIndex: 'sectionHdr',
key: 'sectionHdr',
width: 150,
render: (text) => <Tag color="blue">{text}</Tag>,
},
{
title: 'Submitted At',
dataIndex: 'createdAt',
key: 'createdAt',
render: (text) => text ? new Date(text).toLocaleString('en-IN', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
}) : '-',
sorter: (a, b) => new Date(a.createdAt) - new Date(b.createdAt),
width: 180,
},
{
title: 'WhatsApp',
key: 'whatsapp',
width: 120,
render: (_, record) => {
try {
let contentStr = record.rawData?.Content || record.rawData?.SectionContent || '{}';
// Fallback to HomePageDetails
if (contentStr === '{}' && record.rawData?.HomePageDetails?.length > 0) {
const detail = record.rawData.HomePageDetails.find(d => d.DetailHdr === 'FormData');
if (detail) contentStr = detail.DetailDesc;
}
const content = JSON.parse(contentStr);
return content.whatsapp || <span style={{ color: '#ccc' }}>-</span>;
} catch (e) {
return <span style={{ color: 'red' }}>Error</span>;
}
}
},
{
title: 'Location',
key: 'location',
width: 150,
render: (_, record) => {
try {
let contentStr = record.rawData?.Content || record.rawData?.SectionContent || '{}';
// Fallback to HomePageDetails
if (contentStr === '{}' && record.rawData?.HomePageDetails?.length > 0) {
const detail = record.rawData.HomePageDetails.find(d => d.DetailHdr === 'FormData');
if (detail) contentStr = detail.DetailDesc;
}
const content = JSON.parse(contentStr);
const loc = [content.city, content.state].filter(Boolean).join(', ');
return loc || <span style={{ color: '#ccc' }}>-</span>;
} catch (e) {
return '-';
}
}
},
{
title: 'Form Data',
key: 'formData',
render: (_, record) => {
// Try to parse and show key fields
let content = {};
try {
let contentStr = record.rawData?.Content || record.rawData?.SectionContent || '{}';
// Fallback to HomePageDetails
if (contentStr === '{}' && record.rawData?.HomePageDetails?.length > 0) {
const detail = record.rawData.HomePageDetails.find(d => d.DetailHdr === 'FormData');
if (detail) contentStr = detail.DetailDesc;
}
content = JSON.parse(contentStr);
} catch (e) {
// Ignore parse errors
}
return (
<div style={{ fontSize: 12 }}>
{content.email && <div><strong>Email:</strong> {content.email}</div>}
{content.storeName && <div><strong>Store:</strong> {content.storeName}</div>}
{content.outlets && <div><strong>Outlets:</strong> {content.outlets}</div>}
</div>
);
},
width: 200,
},
{
title: 'Actions',
key: 'actions',
render: (_, record) => (
<Space>
<Button
size="small"
type="primary"
onClick={() => {
console.log('Viewing record:', record); // Debug log
setSelectedRecord(record);
setIsModalVisible(true);
}}
>
View Full Details
</Button>
</Space>
),
width: 150,
},
];
// Recording access columns
const recordingAccessColumns = [
{
title: 'Full Name',
dataIndex: 'fullName',
key: 'fullName',
filteredValue: searchText ? [searchText] : null,
onFilter: (value, record) => {
return (
record.fullName?.toLowerCase().includes(value.toLowerCase()) ||
record.email?.toLowerCase().includes(value.toLowerCase())
);
},
},
{
title: 'Email',
dataIndex: 'email',
key: 'email',
},
{
title: 'Store Name',
dataIndex: 'storeName',
key: 'storeName',
render: (text) => text || '-',
},
{
title: 'UTM Source',
dataIndex: 'utm_source',
key: 'utm_source',
render: (text) => text || '-',
},
{
title: 'UTM Campaign',
dataIndex: 'utm_campaign',
key: 'utm_campaign',
render: (text) => text || '-',
},
{
title: 'Submitted At',
dataIndex: 'submittedAt',
key: 'submittedAt',
render: (text) => new Date(text).toLocaleString('en-IN'),
sorter: (a, b) => new Date(a.submittedAt) - new Date(b.submittedAt),
},
];
// Export to CSV
const exportToCSV = (data, filename) => {
if (!data || data.length === 0) {
message.warning('No data to export');
return;
}
const headers = Object.keys(data[0]).filter(key => !['key', 'rawData'].includes(key));
const csvContent = [
headers.join(','),
...data.map(row =>
headers.map(header => {
const value = row[header];
if (Array.isArray(value)) return `"${value.join('; ')}"`;
if (typeof value === 'object') return `"${JSON.stringify(value)}"`;
return `"${value || ''}"`;
}).join(',')
)
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `${filename}_${new Date().toISOString().split('T')[0]}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
message.success('Exported successfully!');
};
return (
<div className="webinar-submissions-viewer">
<div className="viewer-header">
<div className="header-left">
<h2>Webinar Submissions</h2>
<p>View and manage all webinar registrations and recording access requests</p>
</div>
<div className="header-actions">
<Button
icon={<ReloadOutlined />}
onClick={handleRefresh}
loading={loading}
>
Refresh
</Button>
</div>
</div>
{/* Stats Cards */}
<div className="stats-section">
<div className="stat-card">
<div className="stat-icon primary">
<TeamOutlined />
</div>
<div className="stat-value">{registrations.length}</div>
<div className="stat-label">Total Registrations</div>
</div>
<div className="stat-card">
<div className="stat-icon success">
<PlayCircleOutlined />
</div>
<div className="stat-value">{recordingAccess.length}</div>
<div className="stat-label">Recording Requests</div>
</div>
<div className="stat-card">
<div className="stat-icon info">
<CalendarOutlined />
</div>
<div className="stat-value">
{registrations.length > 0
? new Date(registrations[registrations.length - 1]?.createdAt).toLocaleDateString('en-IN', { month: 'short', day: 'numeric' })
: '-'
}
</div>
<div className="stat-label">Latest Submission</div>
</div>
</div>
<Tabs defaultActiveKey="1">
<TabPane
tab={`Registrations (${registrations.length})`}
key="1"
>
<Space style={{ marginBottom: 16 }}>
<Input
placeholder="Search by name, email, or store"
prefix={<SearchOutlined />}
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
style={{ width: 300 }}
/>
<Button
icon={<DownloadOutlined />}
onClick={() => exportToCSV(registrations, 'webinar_registrations')}
>
Export CSV
</Button>
</Space>
<Table
columns={registrationColumns}
dataSource={registrations}
loading={loading}
scroll={{ x: 1500 }}
pagination={{
pageSize: 10,
showSizeChanger: true,
showTotal: (total) => `Total ${total} registrations`,
}}
/>
</TabPane>
<TabPane
tab={`Recording Access (${recordingAccess.length})`}
key="2"
>
<Space style={{ marginBottom: 16 }}>
<Input
placeholder="Search by name or email"
prefix={<SearchOutlined />}
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
style={{ width: 300 }}
/>
<Button
icon={<DownloadOutlined />}
onClick={() => exportToCSV(recordingAccess, 'recording_access_requests')}
>
Export CSV
</Button>
</Space>
<Table
columns={recordingAccessColumns}
dataSource={recordingAccess}
loading={loading}
scroll={{ x: 1200 }}
pagination={{
pageSize: 10,
showSizeChanger: true,
showTotal: (total) => `Total ${total} requests`,
}}
/>
</TabPane>
</Tabs>
{/* Details Modal */}
<Modal
title="Registration Details"
open={isModalVisible}
onCancel={() => setIsModalVisible(false)}
footer={[
<Button key="close" onClick={() => setIsModalVisible(false)}>
Close
</Button>
]}
width={800}
>
{selectedRecord && (
<Descriptions bordered column={1} size="small">
<Descriptions.Item label="ID">{selectedRecord.id}</Descriptions.Item>
<Descriptions.Item label="Section Name">{selectedRecord.sectionName}</Descriptions.Item>
<Descriptions.Item label="Type">{selectedRecord.sectionHdr}</Descriptions.Item>
<Descriptions.Item label="Description">{selectedRecord.sectionDesc}</Descriptions.Item>
<Descriptions.Item label="Submitted At">
{selectedRecord.createdAt ? new Date(selectedRecord.createdAt).toLocaleString('en-IN') : '-'}
</Descriptions.Item>
{(() => {
try {
let contentStr = selectedRecord.rawData?.Content || selectedRecord.rawData?.SectionContent || '{}';
// Fallback to HomePageDetails
if (contentStr === '{}' && selectedRecord.rawData?.HomePageDetails?.length > 0) {
const detail = selectedRecord.rawData.HomePageDetails.find(d => d.DetailHdr === 'FormData');
if (detail) contentStr = detail.DetailDesc;
}
const content = JSON.parse(contentStr);
// If Content has data, show it
if (Object.keys(content).length > 0) {
return (
<>
<Descriptions.Item label="Full Name">{content.fullName || '-'}</Descriptions.Item>
<Descriptions.Item label="Email">{content.email || '-'}</Descriptions.Item>
<Descriptions.Item label="Store Name">{content.storeName || '-'}</Descriptions.Item>
<Descriptions.Item label="City">{content.city || '-'}</Descriptions.Item>
<Descriptions.Item label="State">{content.state || '-'}</Descriptions.Item>
<Descriptions.Item label="WhatsApp">{content.whatsapp || '-'}</Descriptions.Item>
<Descriptions.Item label="Outlets">{content.outlets || '-'}</Descriptions.Item>
<Descriptions.Item label="Current POS">{content.currentPOS || '-'}</Descriptions.Item>
<Descriptions.Item label="Problems">
{content.problems && Array.isArray(content.problems)
? content.problems.join(', ')
: '-'}
</Descriptions.Item>
<Descriptions.Item label="Language">{content.language || '-'}</Descriptions.Item>
<Descriptions.Item label="UTM Source">{content.utm_source || '-'}</Descriptions.Item>
<Descriptions.Item label="UTM Medium">{content.utm_medium || '-'}</Descriptions.Item>
<Descriptions.Item label="UTM Campaign">{content.utm_campaign || '-'}</Descriptions.Item>
<Descriptions.Item label="Page URL">{content.pageUrl || '-'}</Descriptions.Item>
</>
);
} else {
return (
<>
<Descriptions.Item label="Form Data">
<span style={{ color: '#999' }}>No form data available in Content field</span>
</Descriptions.Item>
<Descriptions.Item label="Raw Data Debug">
<pre style={{ fontSize: '10px', maxHeight: '200px', overflow: 'auto' }}>
{JSON.stringify(selectedRecord.rawData, null, 2)}
</pre>
</Descriptions.Item>
</>
);
}
} catch (e) {
return (
<Descriptions.Item label="Form Data">
<span style={{ color: 'red' }}>Error parsing form data</span>
<pre style={{ fontSize: '10px', maxHeight: '200px', overflow: 'auto' }}>
{JSON.stringify(selectedRecord.rawData, null, 2)}
</pre>
</Descriptions.Item>
);
}
})()}
</Descriptions>
)}
</Modal>
</div>
);
};
export default WebinarSubmissionsViewer;