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