12
This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Typography,
|
||||
Box,
|
||||
LinearProgress,
|
||||
Alert,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemIcon,
|
||||
CircularProgress,
|
||||
Chip
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Publish as PublishIcon,
|
||||
CheckCircle as SuccessIcon,
|
||||
Error as ErrorIcon,
|
||||
Schedule as PendingIcon
|
||||
} from '@mui/icons-material';
|
||||
import { api } from '../../services/api';
|
||||
|
||||
interface BulkWordPressPublishProps {
|
||||
selectedContentIds: string[];
|
||||
contentItems: Array<{
|
||||
id: string;
|
||||
title: string;
|
||||
status: string;
|
||||
}>;
|
||||
onPublishComplete: () => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface BulkPublishResult {
|
||||
total: number;
|
||||
queued: number;
|
||||
skipped: number;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
export const BulkWordPressPublish: React.FC<BulkWordPressPublishProps> = ({
|
||||
selectedContentIds,
|
||||
contentItems,
|
||||
onPublishComplete,
|
||||
onClose
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [publishing, setPublishing] = useState(false);
|
||||
const [result, setResult] = useState<BulkPublishResult | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const selectedItems = contentItems.filter(item =>
|
||||
selectedContentIds.includes(item.id)
|
||||
);
|
||||
|
||||
const handleBulkPublish = async () => {
|
||||
setPublishing(true);
|
||||
setError(null);
|
||||
setResult(null);
|
||||
|
||||
try {
|
||||
const response = await api.post('/api/v1/content/bulk-publish-to-wordpress/', {
|
||||
content_ids: selectedContentIds.map(id => parseInt(id))
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
setResult({
|
||||
total: selectedContentIds.length,
|
||||
queued: response.data.data.content_count,
|
||||
skipped: 0,
|
||||
errors: []
|
||||
});
|
||||
|
||||
// Start polling for individual status updates
|
||||
startStatusPolling();
|
||||
} else {
|
||||
setError(response.data.message || 'Failed to start bulk publishing');
|
||||
}
|
||||
} catch (error: any) {
|
||||
setError(error.response?.data?.message || 'Error starting bulk publish');
|
||||
} finally {
|
||||
setPublishing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const startStatusPolling = () => {
|
||||
// Poll for 2 minutes to check status
|
||||
const pollInterval = setInterval(async () => {
|
||||
try {
|
||||
// Check status of all items (this could be optimized with a dedicated endpoint)
|
||||
const statusPromises = selectedContentIds.map(id =>
|
||||
api.get(`/api/v1/content/${id}/wordpress-status/`)
|
||||
);
|
||||
|
||||
const responses = await Promise.allSettled(statusPromises);
|
||||
|
||||
let completedCount = 0;
|
||||
let successCount = 0;
|
||||
let failedCount = 0;
|
||||
|
||||
responses.forEach((response) => {
|
||||
if (response.status === 'fulfilled' && response.value.data.success) {
|
||||
const status = response.value.data.data.wordpress_sync_status;
|
||||
if (status === 'success' || status === 'failed') {
|
||||
completedCount++;
|
||||
if (status === 'success') successCount++;
|
||||
if (status === 'failed') failedCount++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If all items are complete, stop polling
|
||||
if (completedCount === selectedContentIds.length) {
|
||||
clearInterval(pollInterval);
|
||||
setResult(prev => prev ? {
|
||||
...prev,
|
||||
queued: successCount,
|
||||
errors: Array(failedCount).fill('Publishing failed')
|
||||
} : null);
|
||||
onPublishComplete();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error polling status:', error);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
// Stop polling after 2 minutes
|
||||
setTimeout(() => {
|
||||
clearInterval(pollInterval);
|
||||
}, 120000);
|
||||
};
|
||||
|
||||
const handleOpen = () => {
|
||||
setOpen(true);
|
||||
setResult(null);
|
||||
setError(null);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const getResultSummary = () => {
|
||||
if (!result) return null;
|
||||
|
||||
const { total, queued, skipped, errors } = result;
|
||||
const failed = errors.length;
|
||||
|
||||
return (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Bulk Publish Results
|
||||
</Typography>
|
||||
|
||||
<Box display="flex" gap={1} flexWrap="wrap" mb={2}>
|
||||
<Chip
|
||||
icon={<SuccessIcon />}
|
||||
label={`${queued} Queued`}
|
||||
color="success"
|
||||
size="small"
|
||||
/>
|
||||
{skipped > 0 && (
|
||||
<Chip
|
||||
icon={<PendingIcon />}
|
||||
label={`${skipped} Skipped`}
|
||||
color="warning"
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
{failed > 0 && (
|
||||
<Chip
|
||||
icon={<ErrorIcon />}
|
||||
label={`${failed} Failed`}
|
||||
color="error"
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{failed > 0 && (
|
||||
<Alert severity="warning" sx={{ mt: 1 }}>
|
||||
Some items failed to publish. Check individual item status for details.
|
||||
</Alert>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<PublishIcon />}
|
||||
onClick={handleOpen}
|
||||
disabled={selectedContentIds.length === 0}
|
||||
>
|
||||
Bulk Publish to Site ({selectedContentIds.length})
|
||||
</Button>
|
||||
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>
|
||||
Bulk Publish to Site
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
{!publishing && !result && (
|
||||
<>
|
||||
<Typography variant="body1" gutterBottom>
|
||||
You are about to publish {selectedContentIds.length} content items to your site:
|
||||
</Typography>
|
||||
|
||||
<List dense sx={{ maxHeight: 300, overflow: 'auto', mt: 2 }}>
|
||||
{selectedItems.map((item) => (
|
||||
<ListItem key={item.id}>
|
||||
<ListItemIcon>
|
||||
<PublishIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={item.title}
|
||||
secondary={`Status: ${item.status}`}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
|
||||
<Alert severity="info" sx={{ mt: 2 }}>
|
||||
This will create new posts on your WordPress site with all content,
|
||||
images, categories, and SEO metadata. Items already published will be skipped.
|
||||
</Alert>
|
||||
</>
|
||||
)}
|
||||
|
||||
{publishing && (
|
||||
<Box sx={{ py: 3 }}>
|
||||
<Box display="flex" alignItems="center" gap={2} mb={2}>
|
||||
<CircularProgress size={24} />
|
||||
<Typography>Queuing content for WordPress publishing...</Typography>
|
||||
</Box>
|
||||
<LinearProgress />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{result && getResultSummary()}
|
||||
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mt: 2 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>
|
||||
{result ? 'Close' : 'Cancel'}
|
||||
</Button>
|
||||
|
||||
{!publishing && !result && (
|
||||
<Button
|
||||
onClick={handleBulkPublish}
|
||||
color="primary"
|
||||
variant="contained"
|
||||
disabled={selectedContentIds.length === 0}
|
||||
>
|
||||
Publish All to Site
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BulkWordPressPublish;
|
||||
Reference in New Issue
Block a user