| import React, { useState, useEffect } from 'react'; |
| import { useDispatch, useSelector } from 'react-redux'; |
| import { useNavigate } from 'react-router-dom'; |
| import { |
| fetchSchedules, |
| createSchedule, |
| deleteSchedule, |
| clearError |
| } from '../store/reducers/schedulesSlice'; |
| import { fetchAccounts } from '../store/reducers/accountsSlice'; |
| import { fetchSources } from '../store/reducers/sourcesSlice'; |
| import { formatScheduleForDisplay } from '../utils/timezoneUtils'; |
|
|
| const Schedule = () => { |
| const dispatch = useDispatch(); |
| const navigate = useNavigate(); |
| const { items: schedules, loading, error } = useSelector(state => state.schedules); |
| const { items: accounts } = useSelector(state => state.accounts); |
| const { items: sources } = useSelector(state => state.sources); |
| |
| const [selectedAccount, setSelectedAccount] = useState(''); |
| const [scheduleTime, setScheduleTime] = useState('18:00'); |
| const [selectedDays, setSelectedDays] = useState([]); |
| const [isCreating, setIsCreating] = useState(false); |
| const [userTimezone, setUserTimezone] = useState(''); |
| |
| const daysOfWeek = [ |
| 'Monday', 'Tuesday', 'Wednesday', 'Thursday', |
| 'Friday', 'Saturday', 'Sunday' |
| ]; |
| |
| useEffect(() => { |
| |
| const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; |
| setUserTimezone(timezone); |
| |
| dispatch(fetchSchedules()); |
| dispatch(fetchAccounts()); |
| dispatch(fetchSources()); |
| dispatch(clearError()); |
| }, [dispatch]); |
| |
| const handleDayToggle = (day) => { |
| if (selectedDays.includes(day)) { |
| setSelectedDays(selectedDays.filter(d => d !== day)); |
| } else { |
| setSelectedDays([...selectedDays, day]); |
| } |
| }; |
| |
| const handleCreateSchedule = async (e) => { |
| e.preventDefault(); |
| |
| if (!selectedAccount || selectedDays.length === 0) { |
| return; |
| } |
| |
| setIsCreating(true); |
| |
| try { |
| |
| console.log('Selected account ID:', selectedAccount, 'Type:', typeof selectedAccount); |
| console.log('Available accounts:', accounts); |
| |
| |
| |
| const account = accounts.find(acc => String(acc.id) === String(selectedAccount)); |
| |
| if (!account) { |
| |
| const errorMessage = `Selected account not found. Selected ID: ${selectedAccount}, Available IDs: ${accounts.map(acc => `${acc.id} (${typeof acc.id})`).join(', ')}`; |
| throw new Error(errorMessage); |
| } |
| |
| console.log('[DEBUG] Creating schedule with:', { |
| social_network: selectedAccount, |
| schedule_time: scheduleTime, |
| days: selectedDays, |
| timezone: userTimezone |
| }); |
| |
| await dispatch(createSchedule({ |
| social_network: selectedAccount, |
| schedule_time: scheduleTime, |
| days: selectedDays, |
| timezone: userTimezone |
| })).unwrap(); |
| |
| |
| setSelectedAccount(''); |
| setScheduleTime('18:00'); |
| setSelectedDays([]); |
| } catch (err) { |
| console.error('Failed to create schedule:', err); |
| |
| dispatch(clearError()); |
| dispatch({ |
| type: 'schedules/setError', |
| payload: err.message || 'Failed to create schedule' |
| }); |
| } finally { |
| setIsCreating(false); |
| } |
| }; |
| |
| const handleDeleteSchedule = async (scheduleId) => { |
| try { |
| await dispatch(deleteSchedule(scheduleId)).unwrap(); |
| } catch (err) { |
| console.error('Failed to delete schedule:', err); |
| } |
| }; |
| |
| |
| const getUpcomingSchedules = () => { |
| const today = new Date(); |
| const nextWeek = new Date(today.getTime() + 7 * 24 * 60 * 60 * 1000); |
| |
| return schedules.filter(schedule => { |
| const scheduleDate = new Date(schedule.created_at); |
| return scheduleDate >= today && scheduleDate <= nextWeek; |
| }); |
| }; |
| |
| const upcomingSchedules = getUpcomingSchedules(); |
| |
| return ( |
| <div className="schedule-page min-h-screen bg-gradient-to-br from-gray-50 via-white to-gray-50 p-3 sm:p-4 lg:p-6"> |
| <div className="max-w-7xl mx-auto"> |
| {/* Header Section */} |
| <div className="schedule-header mb-6 sm:mb-8 lg:mb-10 animate-slide-up"> |
| <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-3 sm:mb-4 gap-4"> |
| <div className="flex-1"> |
| <h1 className="schedule-title text-2xl sm:text-3xl lg:text-4xl font-bold bg-gradient-to-r from-gray-900 via-gray-800 to-gray-900 bg-clip-text text-transparent mb-1 sm:mb-2"> |
| Post Scheduling |
| </h1> |
| <p className="schedule-subtitle text-base sm:text-lg text-gray-600 font-medium max-w-2xl sm:max-w-3xl"> |
| Schedule your posts for automatic publishing with intelligent timing |
| </p> |
| </div> |
| <div className="hidden sm:block lg:block"> |
| <div className="w-12 h-12 sm:w-16 sm:h-16 bg-gradient-to-br from-blue-600 to-purple-600 rounded-2xl shadow-lg flex items-center justify-center"> |
| <svg className="w-6 h-6 sm:w-8 sm:h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /> |
| </svg> |
| </div> |
| </div> |
| </div> |
| |
| {/* Stats Cards */} |
| <div className="grid grid-cols-2 sm:grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4 mt-6 sm:mt-8"> |
| <div className="bg-white/80 backdrop-blur-sm rounded-xl p-3 sm:p-4 border border-gray-200/50 shadow-sm hover:shadow-md transition-all duration-300"> |
| <div className="flex items-center justify-between"> |
| <div> |
| <p className="text-xs sm:text-sm font-medium text-gray-600">Total Schedules</p> |
| <p className="text-lg sm:text-2xl font-bold text-gray-900">{schedules.length}</p> |
| </div> |
| <div className="w-8 h-8 sm:w-10 sm:h-10 bg-blue-100 rounded-lg flex items-center justify-center"> |
| <svg className="w-4 h-4 sm:w-5 sm:h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3a1 1 0 011-1h6a1 1 0 011 1v4M8 7h8M8 7l-4 9h16l-4-9" /> |
| </svg> |
| </div> |
| </div> |
| </div> |
| |
| <div className="bg-white/80 backdrop-blur-sm rounded-xl p-3 sm:p-4 border border-gray-200/50 shadow-sm hover:shadow-md transition-all duration-300"> |
| <div className="flex items-center justify-between"> |
| <div> |
| <p className="text-xs sm:text-sm font-medium text-gray-600">Active Days</p> |
| <p className="text-lg sm:text-2xl font-bold text-gray-900"> |
| {schedules.reduce((acc, schedule) => acc + (schedule.days?.length || 0), 0)} |
| </p> |
| </div> |
| <div className="w-8 h-8 sm:w-10 sm:h-10 bg-green-100 rounded-lg flex items-center justify-center"> |
| <svg className="w-4 h-4 sm:w-5 sm:h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" /> |
| </svg> |
| </div> |
| </div> |
| </div> |
| |
| <div className="bg-white/80 backdrop-blur-sm rounded-xl p-3 sm:p-4 border border-gray-200/50 shadow-sm hover:shadow-md transition-all duration-300"> |
| <div className="flex items-center justify-between"> |
| <div> |
| <p className="text-xs sm:text-sm font-medium text-gray-600">This Week</p> |
| <p className="text-lg sm:text-2xl font-bold text-gray-900">{upcomingSchedules.length}</p> |
| </div> |
| <div className="w-8 h-8 sm:w-10 sm:h-10 bg-purple-100 rounded-lg flex items-center justify-center"> |
| <svg className="w-4 h-4 sm:w-5 sm:h-5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" /> |
| </svg> |
| </div> |
| </div> |
| </div> |
| |
| <div className="bg-white/80 backdrop-blur-sm rounded-xl p-3 sm:p-4 border border-gray-200/50 shadow-sm hover:shadow-md transition-all duration-300"> |
| <div className="flex items-center justify-between"> |
| <div> |
| <p className="text-xs sm:text-sm font-medium text-gray-600">Accounts</p> |
| <p className="text-lg sm:text-2xl font-bold text-gray-900">{accounts.length}</p> |
| </div> |
| <div className="w-8 h-8 sm:w-10 sm:h-10 bg-orange-100 rounded-lg flex items-center justify-center"> |
| <svg className="w-4 h-4 sm:w-5 sm:h-5 text-orange-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /> |
| </svg> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| {/* Info Message */} |
| <div className="mb-6 sm:mb-8 animate-fade-in"> |
| <div className="bg-gradient-to-r from-blue-50 to-purple-50 border border-blue-200 rounded-xl p-4 sm:p-6 flex items-start space-x-3 sm:space-x-4"> |
| <div className="flex-shrink-0"> |
| <div className="w-8 h-8 sm:w-10 sm:h-10 bg-blue-100 rounded-lg flex items-center justify-center"> |
| <svg className="w-4 h-4 sm:w-6 sm:h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /> |
| </svg> |
| </div> |
| </div> |
| <div className="flex-1"> |
| <h3 className="text-base sm:text-lg font-semibold text-gray-900 mb-1 sm:mb-2">Scheduling Information</h3> |
| <p className="text-xs sm:text-sm text-gray-700 leading-relaxed"> |
| For guaranteed scheduling, the chosen time must be at least 10 minutes after the current time. |
| For example, if it's 2:00 PM, choose at least 2:10 PM. Otherwise, the task will be scheduled |
| for the next occurrence of the chosen time slot. |
| </p> |
| </div> |
| </div> |
| </div> |
| |
| {/* Error Display */} |
| {error && ( |
| <div className="mb-6 sm:mb-8 animate-fade-in"> |
| <div className="bg-red-50 border border-red-200 rounded-xl p-3 sm:p-4 flex items-start space-x-3"> |
| <div className="flex-shrink-0"> |
| <svg className="w-4 h-4 sm:w-5 sm:h-5 text-red-400" fill="currentColor" viewBox="0 0 20 20"> |
| <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" /> |
| </svg> |
| </div> |
| <div className="flex-1"> |
| <p className="text-xs sm:text-sm font-medium text-red-800">{error}</p> |
| </div> |
| </div> |
| </div> |
| )} |
| |
| <div className="schedule-content space-y-6 sm:space-y-8"> |
| {/* Info Message when no accounts */} |
| {accounts.length === 0 && !loading && ( |
| <div className="mb-6 sm:mb-8 animate-fade-in"> |
| <div className="bg-gradient-to-r from-yellow-50 to-amber-50 border border-yellow-200 rounded-xl p-4 sm:p-6 flex items-start space-x-3 sm:space-x-4"> |
| <div className="flex-shrink-0"> |
| <div className="w-8 h-8 sm:w-10 sm:h-10 bg-yellow-100 rounded-lg flex items-center justify-center"> |
| <svg className="w-4 h-4 sm:w-6 sm:h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> |
| </svg> |
| </div> |
| </div> |
| <div className="flex-1"> |
| <h3 className="text-base sm:text-lg font-semibold text-gray-900 mb-1 sm:mb-2">No Social Accounts Found</h3> |
| <p className="text-xs sm:text-sm text-gray-700 leading-relaxed"> |
| You need to connect at least one social media account before creating schedules. |
| Please go to the <a href="/accounts" className="text-blue-600 hover:underline font-medium">Accounts page</a> to connect your social media accounts. |
| </p> |
| </div> |
| </div> |
| </div> |
| )} |
| |
| {/* Info Message when no sources */} |
| {sources.length === 0 && !loading && ( |
| <div className="mb-6 sm:mb-8 animate-fade-in"> |
| <div className="bg-gradient-to-r from-yellow-50 to-amber-50 border border-yellow-200 rounded-xl p-4 sm:p-6 flex items-start space-x-3 sm:space-x-4"> |
| <div className="flex-shrink-0"> |
| <div className="w-8 h-8 sm:w-10 sm:h-10 bg-yellow-100 rounded-lg flex items-center justify-center"> |
| <svg className="w-4 h-4 sm:w-6 sm:h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> |
| </svg> |
| </div> |
| </div> |
| <div className="flex-1"> |
| <h3 className="text-base sm:text-lg font-semibold text-gray-900 mb-1 sm:mb-2">No RSS Sources Found</h3> |
| <p className="text-xs sm:text-sm text-gray-700 leading-relaxed mb-3"> |
| You need to add at least one RSS source before creating schedules. |
| RSS sources provide the content that will be used for your scheduled posts. |
| </p> |
| <button |
| onClick={() => navigate('/sources')} |
| className="btn btn-primary bg-gradient-to-r from-yellow-500 to-amber-600 text-white py-2 px-4 rounded-xl font-semibold hover:from-yellow-600 hover:to-amber-700 transition-all duration-300 shadow-md hover:shadow-lg flex items-center justify-center space-x-2 touch-manipulation" |
| > |
| <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" /> |
| </svg> |
| <span className="text-xs sm:text-sm">Add RSS Sources</span> |
| </button> |
| </div> |
| </div> |
| </div> |
| )} |
| |
| {/* Schedule Creation Section */} |
| <div className="schedule-creation-section bg-white/90 backdrop-blur-sm rounded-2xl p-4 sm:p-6 shadow-lg border border-gray-200/30 hover:shadow-xl transition-all duration-300 animate-slide-up"> |
| <div className="flex items-center justify-between mb-4 sm:mb-6"> |
| <h2 className="section-title text-xl sm:text-2xl font-bold text-gray-900 flex items-center space-x-2 sm:space-x-3"> |
| <div className="w-6 h-6 sm:w-8 sm:h-8 bg-gradient-to-br from-blue-600 to-purple-600 rounded-lg flex items-center justify-center"> |
| <svg className="w-3 h-3 sm:w-5 sm:h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" /> |
| </svg> |
| </div> |
| <span className="text-sm sm:text-base">Create New Schedule</span> |
| </h2> |
| </div> |
| |
| <form onSubmit={handleCreateSchedule} className="schedule-form space-y-6 sm:space-y-8"> |
| <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-8"> |
| {/* Account Selection */} |
| <div className="form-field"> |
| <label className="form-label block text-xs sm:text-sm font-semibold text-gray-700 mb-2 sm:mb-3">Select Account</label> |
| <select |
| value={selectedAccount} |
| onChange={(e) => setSelectedAccount(e.target.value)} |
| className="form-select w-full px-3 sm:px-4 py-2.5 sm:py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-600 focus:border-transparent transition-all duration-300 bg-white/80 backdrop-blur-sm shadow-sm hover:shadow-md touch-manipulation" |
| disabled={isCreating || accounts.length === 0} |
| aria-label="Select social account" |
| > |
| <option value="">Select a social account</option> |
| {accounts.length === 0 ? ( |
| <option disabled>No accounts available. Please add an account first.</option> |
| ) : ( |
| accounts.map(account => ( |
| <option key={account.id} value={String(account.id)}> |
| {account.account_name} ({account.social_network}) |
| </option> |
| )) |
| )} |
| </select> |
| </div> |
| |
| {/* Time Selection */} |
| <div className="form-field"> |
| <label className="form-label block text-xs sm:text-sm font-semibold text-gray-700 mb-2 sm:mb-3">Schedule Time</label> |
| <div className="relative"> |
| <input |
| type="time" |
| value={scheduleTime} |
| onChange={(e) => setScheduleTime(e.target.value)} |
| className="form-input time-input w-full px-3 sm:px-4 py-2.5 sm:py-3 pr-10 sm:pr-12 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-600 focus:border-transparent transition-all duration-300 bg-white/80 backdrop-blur-sm shadow-sm hover:shadow-md touch-manipulation" |
| disabled={isCreating} |
| aria-label="Select schedule time" |
| /> |
| <div className="absolute inset-y-0 right-0 flex items-center pr-3 sm:pr-4 pointer-events-none"> |
| <svg className="w-4 h-4 sm:w-5 sm:h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /> |
| </svg> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| {/* Days Selection */} |
| <div className="form-field"> |
| <label className="form-label block text-xs sm:text-sm font-semibold text-gray-700 mb-3 sm:mb-4">Select Days</label> |
| <div className="days-selection grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-7 gap-2 sm:gap-3"> |
| {daysOfWeek.map(day => ( |
| <button |
| key={day} |
| type="button" |
| className={`day-button group px-3 py-2.5 sm:py-3 rounded-xl border-2 transition-all duration-300 transform hover:scale-105 active:scale-95 touch-manipulation ${ |
| selectedDays.includes(day) |
| ? 'bg-gradient-to-r from-blue-600 to-purple-600 text-white border-blue-600 shadow-lg' |
| : 'bg-white/80 backdrop-blur-sm text-gray-700 border-gray-300 hover:border-blue-400 hover:shadow-md' |
| }`} |
| onClick={() => handleDayToggle(day)} |
| disabled={isCreating} |
| aria-pressed={selectedDays.includes(day)} |
| aria-label={`Select ${day}`} |
| > |
| <span className="text-xs sm:text-sm font-medium">{day.substring(0, 3)}</span> |
| {selectedDays.includes(day) && ( |
| <div className="mt-0.5 sm:mt-1"> |
| <svg className="w-3 h-3 sm:w-4 sm:h-4 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" /> |
| </svg> |
| </div> |
| )} |
| </button> |
| ))} |
| </div> |
| </div> |
| |
| {/* Submit Button */} |
| <div className="flex justify-center pt-2 sm:pt-4"> |
| <button |
| type="submit" |
| className="btn btn-primary create-button bg-gradient-to-r from-blue-600 to-purple-600 text-white py-2.5 sm:py-3 px-6 sm:px-8 rounded-xl font-semibold hover:from-blue-700 hover:to-purple-700 transition-all duration-300 shadow-lg hover:shadow-xl disabled:opacity-60 disabled:cursor-not-allowed flex items-center justify-center space-x-2 touch-manipulation active:scale-95" |
| disabled={isCreating || !selectedAccount || selectedDays.length === 0 || accounts.length === 0 || sources.length === 0} |
| aria-busy={isCreating} |
| > |
| {isCreating ? ( |
| <> |
| <svg className="animate-spin w-4 h-4 sm:w-5 sm:h-5 text-white" fill="none" viewBox="0 0 24 24"> |
| <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> |
| <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> |
| </svg> |
| <span className="text-xs sm:text-sm">Creating...</span> |
| </> |
| ) : accounts.length === 0 ? ( |
| <> |
| <svg className="w-4 h-4 sm:w-5 sm:h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> |
| </svg> |
| <span className="text-xs sm:text-sm">No Accounts</span> |
| </> |
| ) : sources.length === 0 ? ( |
| <> |
| <svg className="w-4 h-4 sm:w-5 sm:h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> |
| </svg> |
| <span className="text-xs sm:text-sm">No Sources</span> |
| </> |
| ) : ( |
| <> |
| <svg className="w-4 h-4 sm:w-5 sm:h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" /> |
| </svg> |
| <span className="text-xs sm:text-sm">Add Schedule</span> |
| </> |
| )} |
| </button> |
| </div> |
| </form> |
| </div> |
| |
| {} |
| <div className="schedules-list-section bg-white/90 backdrop-blur-sm rounded-2xl p-4 sm:p-6 shadow-lg border border-gray-200/30 hover:shadow-xl transition-all duration-300 animate-slide-up"> |
| <div className="flex items-center justify-between mb-4 sm:mb-6"> |
| <h2 className="section-title text-xl sm:text-2xl font-bold text-gray-900 flex items-center space-x-2 sm:space-x-3"> |
| <div className="w-6 h-6 sm:w-8 sm:h-8 bg-gradient-to-br from-green-500 to-emerald-600 rounded-lg flex items-center justify-center"> |
| <svg className="w-3 h-3 sm:w-5 sm:h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> |
| </svg> |
| </div> |
| <span className="text-sm sm:text-base">Your Schedules</span> |
| </h2> |
| <span className="bg-green-100 text-green-800 text-xs sm:text-sm font-medium px-2 sm:px-3 py-0.5 sm:py-1 rounded-full"> |
| {schedules.length} schedules |
| </span> |
| </div> |
| |
| {loading ? ( |
| <div className="flex items-center justify-center py-8 sm:py-12"> |
| <div className="animate-pulse"> |
| <div className="w-6 h-6 sm:w-8 sm:h-8 bg-gray-300 rounded-full"></div> |
| </div> |
| </div> |
| ) : schedules.length === 0 ? ( |
| <div className="text-center py-8 sm:py-12"> |
| <div className="w-12 h-12 sm:w-16 sm:h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-3 sm:mb-4"> |
| <svg className="w-6 h-6 sm:w-8 sm:h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3a1 1 0 011-1h6a1 1 0 011 1v4M8 7h8M8 7l-4 9h16l-4-9" /> |
| </svg> |
| </div> |
| <h3 className="text-base sm:text-lg font-semibold text-gray-900 mb-1 sm:mb-2">No schedules created yet</h3> |
| <p className="text-xs sm:text-sm text-gray-600">Create your first schedule to automate your social media posting!</p> |
| </div> |
| ) : ( |
| <div className="schedules-list space-y-3 sm:space-y-4"> |
| {schedules |
| .slice() |
| .sort((a, b) => new Date(b.created_at) - new Date(a.created_at)) |
| .map((schedule, index) => ( |
| <div key={schedule.id} className="schedule-item group border border-gray-200 rounded-xl bg-gradient-to-r from-gray-50 to-white hover:from-gray-100 hover:to-white transition-all duration-300 hover:shadow-lg animate-fade-in" style={{animationDelay: `${index * 100}ms`}}> |
| <div className="p-4 sm:p-6"> |
| <div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-4"> |
| <div className="flex-1"> |
| <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6"> |
| <div className="schedule-time"> |
| <div className="flex items-center space-x-2 sm:space-x-3 mb-2"> |
| <div className="w-6 h-6 sm:w-8 sm:h-8 bg-blue-100 rounded-lg flex items-center justify-center"> |
| <svg className="w-3 h-3 sm:w-4 sm:h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /> |
| </svg> |
| </div> |
| <span className="text-xs sm:text-sm font-medium text-gray-600">Time</span> |
| </div> |
| <span className="time-value text-lg sm:text-2xl font-bold text-gray-900"> |
| {formatScheduleForDisplay(schedule.schedule_time)} |
| </span> |
| </div> |
| |
| <div className="schedule-account"> |
| <div className="flex items-center space-x-2 sm:space-x-3 mb-2"> |
| <div className="w-6 h-6 sm:w-8 sm:h-8 bg-purple-100 rounded-lg flex items-center justify-center"> |
| <svg className="w-3 h-3 sm:w-4 sm:h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /> |
| </svg> |
| </div> |
| <span className="text-xs sm:text-sm font-medium text-gray-600">Account</span> |
| </div> |
| <span className="account-value text-base sm:text-lg font-semibold text-gray-900">{schedule.social_network}</span> |
| </div> |
| </div> |
| |
| {/* Days Display */} |
| <div className="mt-4 sm:mt-6"> |
| <div className="flex items-center space-x-2 sm:space-x-3 mb-2 sm:mb-3"> |
| <div className="w-6 h-6 sm:w-8 sm:h-8 bg-green-100 rounded-lg flex items-center justify-center"> |
| <svg className="w-3 h-3 sm:w-4 sm:h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" /> |
| </svg> |
| </div> |
| <span className="text-xs sm:text-sm font-medium text-gray-600">Scheduled Days</span> |
| </div> |
| <div className="flex flex-wrap gap-1.5 sm:gap-2"> |
| {(schedule.days || []).map((day, dayIndex) => ( |
| <span key={dayIndex} className="px-2 py-0.5 sm:px-3 sm:py-1 bg-gradient-to-r from-green-100 to-emerald-100 text-green-800 text-xs sm:text-sm font-medium rounded-full"> |
| {day.substring(0, 3)} |
| </span> |
| ))} |
| </div> |
| </div> |
| </div> |
| |
| <div className="schedule-actions flex-shrink-0"> |
| <button |
| className="btn btn-secondary bg-gradient-to-r from-red-500 to-pink-600 text-white py-2 px-4 sm:px-6 rounded-xl font-semibold hover:from-red-600 hover:to-pink-700 transition-all duration-300 shadow-md hover:shadow-lg disabled:opacity-60 disabled:cursor-not-allowed flex items-center justify-center space-x-2 touch-manipulation active:scale-95" |
| onClick={() => handleDeleteSchedule(schedule.id)} |
| disabled={loading} |
| > |
| <svg className="w-3 h-3 sm:w-4 sm:h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /> |
| </svg> |
| <span className="text-xs sm:text-sm">Delete</span> |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| ))} |
| </div> |
| )} |
| </div> |
| </div> |
| </div> |
| </div> |
| ); |
| }; |
|
|
| export default Schedule; |