205 lines
6.6 KiB
TypeScript
205 lines
6.6 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Modal } from '../ui/modal';
|
|
import Button from '../ui/button/Button';
|
|
import { CalendarIcon, ClockIcon, ErrorIcon } from '../../icons';
|
|
import { getAccountTimezone } from '../../utils/timezone';
|
|
|
|
interface Content {
|
|
id: number;
|
|
title: string;
|
|
}
|
|
|
|
interface BulkScheduleModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
contentItems: Content[];
|
|
onSchedule: (contentIds: number[], scheduledDate: string) => Promise<void>;
|
|
}
|
|
|
|
const BulkScheduleModal: React.FC<BulkScheduleModalProps> = ({
|
|
isOpen,
|
|
onClose,
|
|
contentItems,
|
|
onSchedule
|
|
}) => {
|
|
const [selectedDate, setSelectedDate] = useState<string>('');
|
|
const [selectedTime, setSelectedTime] = useState<string>('09:00');
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
// Default to tomorrow at 9:00 AM
|
|
const tomorrow = new Date();
|
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
const dateStr = tomorrow.toISOString().split('T')[0];
|
|
setSelectedDate(dateStr);
|
|
setSelectedTime('09:00');
|
|
}
|
|
}, [isOpen]);
|
|
|
|
const handleSubmit = async () => {
|
|
if (!selectedDate || !selectedTime || contentItems.length === 0) return;
|
|
|
|
// Combine date and time into ISO string
|
|
const scheduledDateTime = new Date(`${selectedDate}T${selectedTime}`);
|
|
|
|
// Validate future date
|
|
if (scheduledDateTime <= new Date()) {
|
|
alert('Please select a future date and time');
|
|
return;
|
|
}
|
|
|
|
setIsSubmitting(true);
|
|
try {
|
|
const contentIds = contentItems.map(item => item.id);
|
|
await onSchedule(contentIds, scheduledDateTime.toISOString());
|
|
onClose();
|
|
} catch (error) {
|
|
console.error('Failed to schedule:', error);
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const formatPreviewDate = () => {
|
|
if (!selectedDate || !selectedTime) return '';
|
|
|
|
try {
|
|
const dateTime = new Date(`${selectedDate}T${selectedTime}`);
|
|
return dateTime.toLocaleString('en-US', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
hour: 'numeric',
|
|
minute: '2-digit',
|
|
hour12: true,
|
|
timeZone: getAccountTimezone(),
|
|
});
|
|
} catch (error) {
|
|
return '';
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Modal
|
|
isOpen={isOpen}
|
|
onClose={onClose}
|
|
showCloseButton={!isSubmitting}
|
|
>
|
|
<div className="p-6">
|
|
{/* Header */}
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<CalendarIcon className="w-8 h-8 text-primary-500" />
|
|
<div>
|
|
<h2 className="text-xl font-semibold text-gray-900">
|
|
Schedule {contentItems.length} Article{contentItems.length !== 1 ? 's' : ''}
|
|
</h2>
|
|
<p className="text-sm text-gray-500 mt-1">
|
|
Schedule all selected articles for the same date and time
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Date/Time Selection */}
|
|
<div className="space-y-4">
|
|
{/* Date Picker */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Schedule Date
|
|
</label>
|
|
<div className="relative">
|
|
<input
|
|
type="date"
|
|
value={selectedDate}
|
|
onChange={(e) => setSelectedDate(e.target.value)}
|
|
min={new Date().toISOString().split('T')[0]}
|
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
|
disabled={isSubmitting}
|
|
/>
|
|
<CalendarIcon className="absolute right-3 top-2.5 w-5 h-5 text-gray-400 pointer-events-none" />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Time Picker */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Schedule Time
|
|
</label>
|
|
<div className="relative">
|
|
<input
|
|
type="time"
|
|
value={selectedTime}
|
|
onChange={(e) => setSelectedTime(e.target.value)}
|
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
|
disabled={isSubmitting}
|
|
/>
|
|
<ClockIcon className="absolute right-3 top-2.5 w-5 h-5 text-gray-400 pointer-events-none" />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Preview */}
|
|
{selectedDate && selectedTime && (
|
|
<div className="bg-blue-50 border-l-4 border-blue-500 p-4 rounded">
|
|
<p className="text-sm font-medium text-blue-900">
|
|
Preview: {formatPreviewDate()} ({getAccountTimezone()})
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Warning */}
|
|
<div className="bg-yellow-50 border-l-4 border-yellow-500 p-4 rounded">
|
|
<div className="flex items-start gap-2">
|
|
<ErrorIcon className="w-5 h-5 text-yellow-600 flex-shrink-0 mt-0.5" />
|
|
<p className="text-sm text-yellow-800">
|
|
All {contentItems.length} article{contentItems.length !== 1 ? 's' : ''} will be scheduled for the same date and time.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content List */}
|
|
{contentItems.length > 0 && (
|
|
<div className="border border-gray-200 rounded-lg p-4 max-h-48 overflow-y-auto">
|
|
<p className="text-sm font-medium text-gray-700 mb-2">
|
|
Selected Articles:
|
|
</p>
|
|
<ul className="space-y-1">
|
|
{contentItems.slice(0, 10).map((item, index) => (
|
|
<li key={item.id} className="text-sm text-gray-600 truncate">
|
|
{index + 1}. {item.title}
|
|
</li>
|
|
))}
|
|
{contentItems.length > 10 && (
|
|
<li className="text-sm text-gray-500 italic">
|
|
... and {contentItems.length - 10} more
|
|
</li>
|
|
)}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<div className="flex gap-3 mt-6 justify-end">
|
|
<Button
|
|
variant="outline"
|
|
onClick={onClose}
|
|
disabled={isSubmitting}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
variant="primary"
|
|
onClick={handleSubmit}
|
|
disabled={isSubmitting || !selectedDate || !selectedTime}
|
|
>
|
|
{isSubmitting ? 'Scheduling...' : 'Schedule All'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
export default BulkScheduleModal;
|