diff --git a/app/admin/dashboard/page.tsx b/app/admin/dashboard/page.tsx index 35c7ca5..18167a3 100644 --- a/app/admin/dashboard/page.tsx +++ b/app/admin/dashboard/page.tsx @@ -2,7 +2,8 @@ import { useAuth } from '@/contexts/AuthContext'; import Link from 'next/link'; -import { UsersIcon, CubeIcon, TagIcon } from '@heroicons/react/24/outline'; +import { UsersIcon, CubeIcon, TagIcon , BellIcon } from '@heroicons/react/24/outline'; +// import { BellIcon } from 'lucide-react'; export default function Dashboard() { const { user, logout } = useAuth(); @@ -57,6 +58,15 @@ export default function Dashboard() {

+ +
+ +

مدیریت یادآوری‌ها

+

+ ایجاد و مدیریت یادآوری‌ها و پیام‌های انگیزشی +

+
+ diff --git a/app/admin/reminders/page.tsx b/app/admin/reminders/page.tsx new file mode 100644 index 0000000..a5b7016 --- /dev/null +++ b/app/admin/reminders/page.tsx @@ -0,0 +1,247 @@ +'use client'; + +import { useState, useEffect, useCallback, useRef } from 'react'; +import { useAuth } from '@/contexts/AuthContext'; +import { packagesApi } from '@/lib/api/packages'; +import { remindersApi } from '@/lib/api/reminders'; +import ReminderList from '@/components/admin/reminders/ReminderList'; +import ReminderForm from '@/components/admin/reminders/ReminderForm'; +import { PackageName } from '@/types/package'; +import { Reminder, CreateReminderData, UpdateReminderData } from '@/types/reminder'; +import { PlusIcon } from '@heroicons/react/24/outline'; + +export default function RemindersPage() { + const { token } = useAuth(); + const [packages, setPackages] = useState([]); + const [selectedPackage, setSelectedPackage] = useState(null); + const [reminders, setReminders] = useState([]); + const [selectedReminder, setSelectedReminder] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [isLoadingPackages, setIsLoadingPackages] = useState(false); + const [isFormVisible, setIsFormVisible] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(null); + + const initialLoadRef = useRef(false); + const prevPackageRef = useRef(null); + + // Load packages on mount + useEffect(() => { + if (token && !initialLoadRef.current) { + initialLoadRef.current = true; + loadPackages(); + } + }, [token]); + + const loadPackages = async () => { + setIsLoadingPackages(true); + setError(null); + try { + const data = await packagesApi.getAllPackages(token!); + setPackages(data); + if (data.length > 0 && !selectedPackage) { + setSelectedPackage(data[0]); + } + } catch (err) { + setError('خطا در دریافت لیست پکیج‌ها'); + } finally { + setIsLoadingPackages(false); + } + }; + + const loadReminders = useCallback(async (packageName: string, force = false) => { + if (!force && prevPackageRef.current === packageName) { + return; + } + + setIsLoading(true); + setError(null); + prevPackageRef.current = packageName; + + try { + const data = await remindersApi.getReminders(packageName, token!); + setReminders(data); + } catch (err) { + setError(err instanceof Error ? err.message : 'خطا در دریافت یادآوری‌ها'); + } finally { + setIsLoading(false); + } + }, [token]); + + // Load reminders when package changes + useEffect(() => { + if (selectedPackage?.name && token) { + loadReminders(selectedPackage.name); + } else { + setReminders([]); + } + }, [selectedPackage?.name, token, loadReminders]); + + const handlePackageChange = (e: React.ChangeEvent) => { + const pkg = packages.find(p => p.name === e.target.value); + setSelectedPackage(pkg || null); + setIsFormVisible(false); + setSelectedReminder(null); + setError(null); + setSuccess(null); + }; + + const handleCreate = () => { + setSelectedReminder(null); + setIsFormVisible(true); + }; + + const handleEdit = (reminder: Reminder) => { + setSelectedReminder(reminder); + setIsFormVisible(true); + }; + + const handleDelete = async (reminder: Reminder) => { + if (!selectedPackage) return; + + if (!confirm(`آیا از حذف یادآوری "${reminder.title}" اطمینان دارید؟`)) { + return; + } + + setIsLoading(true); + setError(null); + setSuccess(null); + + try { + await remindersApi.deleteReminder(selectedPackage.name!, reminder.id, token!); + setSuccess('یادآوری با موفقیت حذف شد'); + if (selectedPackage.name) { + prevPackageRef.current = null; + await loadReminders(selectedPackage.name, true); + } + } catch (err) { + setError(err instanceof Error ? err.message : 'خطا در حذف یادآوری'); + } finally { + setIsLoading(false); + } + }; + + const handleSubmit = async (data: CreateReminderData | UpdateReminderData) => { + if (!selectedPackage) return; + + setIsLoading(true); + setError(null); + setSuccess(null); + + try { + if (selectedReminder) { + // Update existing reminder + await remindersApi.updateReminder(selectedPackage.name!, selectedReminder.id, data, token!); + setSuccess('یادآوری با موفقیت به‌روزرسانی شد'); + } else { + // Create new reminder + await remindersApi.createReminder(selectedPackage.name!, data as CreateReminderData, token!); + setSuccess('یادآوری با موفقیت ایجاد شد'); + } + + if (selectedPackage.name) { + prevPackageRef.current = null; + await loadReminders(selectedPackage.name, true); + } + + setIsFormVisible(false); + setSelectedReminder(null); + } catch (err) { + setError(err instanceof Error ? err.message : 'خطا در ذخیره یادآوری'); + } finally { + setIsLoading(false); + } + }; + + const handleCancel = () => { + setIsFormVisible(false); + setSelectedReminder(null); + setError(null); + setSuccess(null); + }; + + return ( +
+
+
+

+ مدیریت یادآوری‌ها +

+
+ + {/* Package Selector */} +
+ + +
+ + {/* Messages */} + {error && ( +
+

{error}

+
+ )} + + {success && ( +
+

{success}

+
+ )} + + {/* Form or List */} + {selectedPackage && ( + <> + {!isFormVisible && ( +
+ +
+ )} + + {isFormVisible ? ( + + ) : ( + + )} + + )} +
+
+ ); +} \ No newline at end of file diff --git a/components/admin/reminders/ReminderForm.tsx b/components/admin/reminders/ReminderForm.tsx new file mode 100644 index 0000000..4e909ae --- /dev/null +++ b/components/admin/reminders/ReminderForm.tsx @@ -0,0 +1,243 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Reminder, CreateReminderData, UpdateReminderData, REMINDER_TYPES } from '@/types/reminder'; + +interface ReminderFormProps { + reminder?: Reminder | null; + packageName: string; + onSubmit: (data: CreateReminderData | UpdateReminderData) => Promise; + onCancel: () => void; + isLoading: boolean; +} + +export default function ReminderForm({ reminder, packageName, onSubmit, onCancel, isLoading }: ReminderFormProps) { + const [formData, setFormData] = useState({ + title: '', + description: '', + time: '', + repeatable: false, + repeat_days: null, + type: 'reminder', + }); + + useEffect(() => { + if (reminder) { + // Format datetime for input field (remove milliseconds and timezone) + // Convert from "2025-12-13T15:30:00.000" to "2025-12-13T15:30" for input + const formattedTime = reminder.time ? reminder.time.replace(/\.\d+/, '').slice(0, 16) : ''; + + setFormData({ + title: reminder.title || '', + description: reminder.description || '', + time: formattedTime, + repeatable: reminder.repeatable || false, + repeat_days: reminder.repeat_days, + type: reminder.type || 'reminder', + }); + } else { + // Set default time to current date + 1 hour + const defaultTime = new Date(); + defaultTime.setHours(defaultTime.getHours() + 1); + + // Format as YYYY-MM-DDTHH:MM for datetime-local input + const year = defaultTime.getFullYear(); + const month = String(defaultTime.getMonth() + 1).padStart(2, '0'); + const day = String(defaultTime.getDate()).padStart(2, '0'); + const hours = String(defaultTime.getHours()).padStart(2, '0'); + const minutes = String(defaultTime.getMinutes()).padStart(2, '0'); + + const formattedDefaultTime = `${year}-${month}-${day}T${hours}:${minutes}`; + + setFormData({ + title: '', + description: '', + time: formattedDefaultTime, + repeatable: false, + repeat_days: null, + type: 'reminder', + }); + } + }, [reminder]); + + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value, type } = e.target; + + if (type === 'checkbox') { + const checked = (e.target as HTMLInputElement).checked; + setFormData(prev => ({ + ...prev, + [name]: checked, + })); + } else { + setFormData(prev => ({ + ...prev, + [name]: value, + })); + } + }; + + const handleNumberChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData(prev => ({ + ...prev, + [name]: value ? parseInt(value, 10) : null, + })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // Format the time properly for API: "2025-12-13 20:00:00.000" + let formattedTime = ''; + + if (formData.time) { + // Input comes as "2025-12-13T20:00" from datetime-local + // Replace 'T' with space and add milliseconds + formattedTime = formData.time.replace('T', ' ') + ':00.000'; + } + + const submitData = { + ...formData, + time: formattedTime, + }; + + await onSubmit(submitData); + }; + + return ( +
+
+

+ {reminder ? 'ویرایش یادآوری' : 'ایجاد یادآوری جدید'} برای پکیج {packageName} +

+
+ +
+ {/* Title */} +
+ + +
+ + {/* Type */} +
+ + +
+ + {/* Time */} +
+ + +
+ + {/* Repeatable */} +
+
+ + +
+
+ + {/* Repeat Days */} + {formData.repeatable && ( +
+ + +
+ )} +
+ + {/* Description */} +
+ +