188 lines
5.9 KiB
TypeScript
188 lines
5.9 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Modal } from '../ui/modal';
|
|
import Button from '../ui/button/Button';
|
|
import { CalendarIcon, ClockIcon } from '../../icons';
|
|
import { getAccountTimezone } from '../../utils/timezone';
|
|
|
|
interface Content {
|
|
id: number;
|
|
title: string;
|
|
scheduled_publish_at?: string | null;
|
|
}
|
|
|
|
interface ScheduleContentModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
content: Content | null;
|
|
onSchedule: (contentId: number, scheduledDate: string) => Promise<void>;
|
|
mode?: 'schedule' | 'reschedule';
|
|
}
|
|
|
|
const ScheduleContentModal: React.FC<ScheduleContentModalProps> = ({
|
|
isOpen,
|
|
onClose,
|
|
content,
|
|
onSchedule,
|
|
mode = 'schedule'
|
|
}) => {
|
|
const [selectedDate, setSelectedDate] = useState<string>('');
|
|
const [selectedTime, setSelectedTime] = useState<string>('09:00');
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (isOpen && content) {
|
|
if (mode === 'reschedule' && content.scheduled_publish_at) {
|
|
// Pre-fill with existing schedule
|
|
const existingDate = new Date(content.scheduled_publish_at);
|
|
const dateStr = existingDate.toISOString().split('T')[0];
|
|
const hours = existingDate.getHours().toString().padStart(2, '0');
|
|
const minutes = existingDate.getMinutes().toString().padStart(2, '0');
|
|
setSelectedDate(dateStr);
|
|
setSelectedTime(`${hours}:${minutes}`);
|
|
} else {
|
|
// 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, content, mode]);
|
|
|
|
const handleSubmit = async () => {
|
|
if (!content || !selectedDate || !selectedTime) 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 {
|
|
await onSchedule(content.id, 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 '';
|
|
}
|
|
};
|
|
|
|
if (!content) return null;
|
|
|
|
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">
|
|
{mode === 'reschedule' ? 'Reschedule' : 'Schedule'} Content Publishing
|
|
</h2>
|
|
<p className="text-sm text-gray-500 mt-1">
|
|
Content: "{content.title}"
|
|
</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-brand-50 border-l-4 border-brand-500 p-4 rounded">
|
|
<p className="text-sm font-medium text-brand-900">
|
|
Preview: {formatPreviewDate()} ({getAccountTimezone()})
|
|
</p>
|
|
</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...' : (mode === 'reschedule' ? 'Reschedule' : 'Schedule')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
export default ScheduleContentModal;
|