Files
igny8/frontend/src/components/common/BulkScheduleModal.tsx
2026-01-19 15:37:03 +00:00

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;