import * as React from 'react'; import { AIGCVideo, ScheduledPost, SocialAccount } from '../types'; import { Icon } from './ui/Icon'; import { format, startOfMonth, endOfMonth, startOfWeek, endOfWeek, eachDayOfInterval, addMonths, subMonths, addWeeks, subWeeks, addDays, subDays, isSameMonth, isToday, parseISO, isPast, getDay, isSameDay } from 'date-fns'; import { useTranslation } from '../hooks/useI18n'; const initialSocialAccounts: SocialAccount[] = [ { id: 'tw', platform: 'Twitter', username: 'greenpage_ai', icon: Twitter }, { id: 'fb', platform: 'Facebook', username: 'GreenPageApp', icon: Facebook }, { id: 'ig', platform: 'Instagram', username: 'greenpage.ai', icon: Instagram }, ]; const CheckCircleIcon: React.FC<{ className?: string }> = ({ className }) => ( ); // --- MODAL COMPONENTS --- const SchedulePostModal: React.FC<{ video: AIGCVideo | null; post: ScheduledPost | null; date: string; accounts: SocialAccount[]; onClose: () => void; onSchedule: (post: Omit) => void; onUpdate: (post: ScheduledPost) => void; onDelete: (postId: string) => void; }> = ({ video, post, date, accounts, onClose, onSchedule, onUpdate, onDelete }) => { const { t, dateLocale } = useTranslation(); const [time, setTime] = React.useState(post ? post.time : '10:00'); const [caption, setCaption] = React.useState(post ? post.caption : (video ? `Check out our new video: ${video.title}!` : "")); const [selectedAccounts, setSelectedAccounts] = React.useState(post ? post.socialAccountIds : []); const handleAccountToggle = (accountId: string) => { setSelectedAccounts(prev => prev.includes(accountId) ? prev.filter(id => id !== accountId) : [...prev, accountId] ); }; const handleSubmit = () => { if (selectedAccounts.length === 0 || !time) { alert('Please select at least one social account and set a time.'); return; } if (post) { onUpdate({ ...post, time, caption, socialAccountIds: selectedAccounts }); } else if (video) { onSchedule({ videoId: video.id, date, time, caption, socialAccountIds: selectedAccounts }); } onClose(); }; if (!video) return null; return ( onClose()}> e.stopPropagation()}> {post ? t('aigc.scheduler.title') : t('aigc.scheduler.title')} {`${t('for')} ${format(parseISO(date), 'MMMM d, yyyy', { locale: dateLocale })}`} {video.title} {t('aigc.scheduler.time')} setTime(e.target.value)} className="w-full bg-gray-100 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600"/> {t('aigc.scheduler.caption')} setCaption(e.target.value)} className="w-full bg-gray-100 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600" /> {t('aigc.scheduler.post_to')} {accounts.map(acc => ( handleAccountToggle(acc.id)} /> {acc.icon} {acc.platform} @{acc.username} ))} {post && { onDelete(post.id); onClose(); }} className="px-4 py-2 rounded-md text-sm font-semibold text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/50">{t('delete')}} {t('cancel')} {post ? t('update') : t('aigc.scheduler.schedule')} ); }; const VideoLibrarySidebar: React.FC<{ videos: AIGCVideo[]; onDragStart: (e: React.DragEvent, videoId: string) => void; activeFilter: 'all' | 'scheduled' | 'unscheduled'; onFilterChange: (filter: 'all' | 'scheduled' | 'unscheduled') => void; }> = ({ videos, onDragStart, activeFilter, onFilterChange }) => { const { t } = useTranslation(); return ( ); }; // --- CALENDAR COMPONENTS --- const CalendarHeader: React.FC<{ view: string; currentDate: Date; onPrev: () => void; onNext: () => void; onViewChange: (view: 'month' | 'week' | 'day') => void; }> = ({ view, currentDate, onPrev, onNext, onViewChange }) => { const { dateLocale } = useTranslation(); const formatString = view === 'month' ? 'MMMM yyyy' : (view === 'week' ? "MMM d, yyyy" : 'MMMM d, yyyy'); return ( {format(currentDate, formatString, { locale: dateLocale })} {view === 'week' && {`Week of ${format(startOfWeek(currentDate, { locale: dateLocale }), 'MMM d')} - ${format(endOfWeek(currentDate, { locale: dateLocale }), 'MMM d')}`}} {(['month', 'week', 'day'] as const).map(v => ( onViewChange(v)} className={`px-3 py-1 text-sm rounded-md capitalize ${view === v ? 'bg-brand-primary text-white' : 'hover:bg-gray-300 dark:hover:bg-gray-600'}`}>{v} ))} ); }; const ScheduledPostItem: React.FC<{ post: ScheduledPost; video: AIGCVideo | undefined; onClick: (e: React.MouseEvent) => void; }> = ({ post, video, onClick }) => { if (!video) return null; const postDate = parseISO(`${post.date}T${post.time}`); const isDistributed = post.status === 'distributed' || isPast(postDate); return ( {isDistributed ? : } {post.time} {video.title} ); }; const Calendar: React.FC<{ view: 'month' | 'week' | 'day'; currentDate: Date; schedule: ScheduledPost[]; videos: AIGCVideo[]; onDateSelect: (date: string) => void; onPostSelect: (post: ScheduledPost) => void; onDrop: (e: React.DragEvent, date: string) => void; }> = ({ view, currentDate, schedule, videos, onDateSelect, onPostSelect, onDrop }) => { const { dateLocale } = useTranslation(); const [dragOverDate, setDragOverDate] = React.useState(null); const renderMonthView = () => { const monthStart = startOfMonth(currentDate); const monthEnd = endOfMonth(monthStart); const days = eachDayOfInterval({ start: startOfWeek(monthStart, { locale: dateLocale }), end: endOfWeek(monthEnd, { locale: dateLocale }) }); const weekdays = eachDayOfInterval({ start: startOfWeek(currentDate, { locale: dateLocale }), end: endOfWeek(currentDate, { locale: dateLocale }), }); return ( <> {weekdays.map(day => {format(day, 'E', { locale: dateLocale })})} {days.map(day => { const dayStr = format(day, 'yyyy-MM-dd'); const isDragOver = dragOverDate === dayStr; const postsForDay = schedule.filter(p => isSameDay(parseISO(p.date), day)).sort((a,b) => a.time.localeCompare(b.time)); return ( { e.preventDefault(); setDragOverDate(dayStr); }} onDragLeave={(e) => { e.preventDefault(); setDragOverDate(null); }} onDragOver={e => e.preventDefault()} onDrop={e => { onDrop(e, dayStr); setDragOverDate(null); }} onClick={() => onDateSelect(dayStr)} className={`relative border-r border-b border-gray-200 dark:border-gray-700 p-2 transition-all duration-200 ${!isSameMonth(day, currentDate) ? 'bg-gray-50 dark:bg-gray-900/50' : ''} ${isDragOver ? 'bg-brand-primary/20 border-2 border-brand-primary' : ''}`} > {format(day, 'd')} {postsForDay.map(post => v.id === post.videoId)} onClick={e => { e.stopPropagation(); onPostSelect(post); }} />)} ); })} > ); }; // Day and Week views could be implemented similarly if needed return ( {renderMonthView()} ); }; // --- MAIN COMPONENT --- interface ContentSchedulerProps { videos: AIGCVideo[]; schedule: ScheduledPost[]; onScheduleUpdate: (newSchedule: ScheduledPost[]) => void; } export const ContentScheduler: React.FC = ({ videos, schedule, onScheduleUpdate }) => { const [currentDate, setCurrentDate] = React.useState(new Date()); const [view, setView] = React.useState<'month' | 'week' | 'day'>('month'); const [isModalOpen, setIsModalOpen] = React.useState(false); const [modalData, setModalData] = React.useState<{ videoId: string | null; postId: string | null; date: string }>({ videoId: null, postId: null, date: '' }); const [videoFilter, setVideoFilter] = React.useState<'all' | 'scheduled' | 'unscheduled'>('all'); const scheduledVideoIds = React.useMemo(() => new Set(schedule.map(p => p.videoId)), [schedule]); const filteredVideos = React.useMemo(() => { if (videoFilter === 'scheduled') { return videos.filter(v => scheduledVideoIds.has(v.id)); } if (videoFilter === 'unscheduled') { return videos.filter(v => !scheduledVideoIds.has(v.id)); } return videos; }, [videos, videoFilter, scheduledVideoIds]); const handleSchedule = (newPostData: Omit) => { const newPost: ScheduledPost = { ...newPostData, id: `post-${Date.now()}`, status: 'scheduled' }; onScheduleUpdate([...schedule, newPost]); }; const handleUpdate = (updatedPost: ScheduledPost) => onScheduleUpdate(schedule.map(p => p.id === updatedPost.id ? updatedPost : p)); const handleDelete = (postId: string) => onScheduleUpdate(schedule.filter(p => p.id !== postId)); const handleDragStart = (e: React.DragEvent, videoId: string) => e.dataTransfer.setData("videoId", videoId); const handleDrop = (e: React.DragEvent, date: string) => { const videoId = e.dataTransfer.getData("videoId"); if(videoId) { setModalData({ videoId, postId: null, date }); setIsModalOpen(true); } }; const changeDate = (direction: 'prev' | 'next') => { const op = direction === 'prev' ? 'sub' : 'add'; const unit = view === 'month' ? 'Months' : view === 'week' ? 'Weeks' : 'Days'; const fn = { sub: { Months: subMonths, Weeks: subWeeks, Days: subDays }, add: { Months: addMonths, Weeks: addWeeks, Days: addDays } }[op][unit]; setCurrentDate(fn(currentDate, 1)); }; const modalPost = modalData.postId ? schedule.find(p => p.id === modalData.postId) : null; const modalVideo = videos.find(v => v.id === (modalPost ? modalPost.videoId : modalData.videoId)); return ( {isModalOpen && setIsModalOpen(false)} onSchedule={handleSchedule} onUpdate={handleUpdate} onDelete={handleDelete} />} changeDate('prev')} onNext={() => changeDate('next')} onViewChange={setView} /> { setModalData({ videoId: null, postId: null, date }); setIsModalOpen(true); }} onPostSelect={post => { setModalData({ videoId: null, postId: post.id, date: post.date }); setIsModalOpen(true); }} onDrop={handleDrop} /> ); };
{`${t('for')} ${format(parseISO(date), 'MMMM d, yyyy', { locale: dateLocale })}`}
{video.title}
{`Week of ${format(startOfWeek(currentDate, { locale: dateLocale }), 'MMM d')} - ${format(endOfWeek(currentDate, { locale: dateLocale }), 'MMM d')}`}
{post.time}