feat: add feature notification

This commit is contained in:
2026-02-23 11:09:57 +03:30
parent c2b55647aa
commit d65b73a593
8 changed files with 743 additions and 2 deletions
+11 -1
View File
@@ -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() {
</p>
</div>
</Link>
<Link href="/admin/reminders" className="block">
<div className="bg-yellow-50 hover:bg-yellow-100 rounded-lg p-6 transition-colors">
<BellIcon className="h-8 w-8 text-yellow-600 mb-3" />
<h3 className="text-lg font-medium text-gray-900">مدیریت یادآوریها</h3>
<p className="text-sm text-gray-600 mt-1">
ایجاد و مدیریت یادآوریها و پیامهای انگیزشی
</p>
</div>
</Link>
</div>
</div>
</div>
+247
View File
@@ -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<PackageName[]>([]);
const [selectedPackage, setSelectedPackage] = useState<PackageName | null>(null);
const [reminders, setReminders] = useState<Reminder[]>([]);
const [selectedReminder, setSelectedReminder] = useState<Reminder | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [isLoadingPackages, setIsLoadingPackages] = useState(false);
const [isFormVisible, setIsFormVisible] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
const initialLoadRef = useRef(false);
const prevPackageRef = useRef<string | null>(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<HTMLSelectElement>) => {
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 (
<div className="p-6">
<div className="max-w-7xl mx-auto">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold text-gray-900">
مدیریت یادآوریها
</h1>
</div>
{/* Package Selector */}
<div className="bg-white p-4 rounded-lg shadow mb-6">
<label htmlFor="package" className="block text-sm font-medium text-gray-700 mb-2">
انتخاب پکیج
</label>
<select
id="package"
value={selectedPackage?.name || ''}
onChange={handlePackageChange}
disabled={isLoadingPackages}
className="block w-full px-3 py-2 text-slate-900 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm disabled:opacity-50"
>
{isLoadingPackages ? (
<option>در حال بارگذاری پکیجها...</option>
) : (
packages.map((pkg) => (
<option key={pkg.id} value={pkg.name || ''}>
{pkg.title} ({pkg.name})
</option>
))
)}
</select>
</div>
{/* Messages */}
{error && (
<div className="mb-4 rounded-md bg-red-50 p-4">
<p className="text-sm text-red-800">{error}</p>
</div>
)}
{success && (
<div className="mb-4 rounded-md bg-green-50 p-4">
<p className="text-sm text-green-800">{success}</p>
</div>
)}
{/* Form or List */}
{selectedPackage && (
<>
{!isFormVisible && (
<div className="mb-4 flex justify-end">
<button
onClick={handleCreate}
disabled={isLoading}
className="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
>
<PlusIcon className="h-5 w-5 ml-2" />
یادآوری جدید
</button>
</div>
)}
{isFormVisible ? (
<ReminderForm
reminder={selectedReminder}
packageName={selectedPackage.name!}
onSubmit={handleSubmit}
onCancel={handleCancel}
isLoading={isLoading}
/>
) : (
<ReminderList
reminders={reminders}
packageName={selectedPackage.name!}
onEdit={handleEdit}
onDelete={handleDelete}
isLoading={isLoading}
/>
)}
</>
)}
</div>
</div>
);
}