Add enhanced search/filtering and push notifications for prayer reminders
This commit is contained in:
1 parent
aa3f42479f
commit
1a160135d7
5 files changed
+294
-38
No files matched your search
+169
-36
@@ -6,7 +6,7 @@ import Login from '@/components/Login';
|
||||
import Admin from '@/components/Admin';
|
||||
import Profile from '@/components/Profile';
|
||||
import { db } from '@/lib/firebase';
|
||||
import { doc, setDoc, collection, addDoc, getDocs, updateDoc, query, where } from 'firebase/firestore';
|
||||
import { doc, setDoc, collection, addDoc, getDocs, updateDoc, query, where, getDoc } from 'firebase/firestore';
|
||||
import emailjs from '@emailjs/browser';
|
||||
import jsPDF from 'jspdf';
|
||||
import { CSVLink } from 'react-csv';
|
||||
@@ -23,6 +23,8 @@ interface Prayer {
|
||||
archived?: boolean;
|
||||
reminderFrequency?: 'daily' | 'weekly' | 'monthly' | 'never';
|
||||
includeActiveSummary?: boolean;
|
||||
answered?: boolean;
|
||||
answeredDate?: string;
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
@@ -47,6 +49,10 @@ export default function Home() {
|
||||
const { user, loading, logout, isAdmin } = useAuth();
|
||||
const [showLoadingScreen, setShowLoadingScreen] = useState(true);
|
||||
const [editingPrayer, setEditingPrayer] = useState<Prayer | null>(null);
|
||||
const [dateFrom, setDateFrom] = useState('');
|
||||
const [dateTo, setDateTo] = useState('');
|
||||
const [typeFilter, setTypeFilter] = useState<'all' | 'prayer' | 'praise'>('all');
|
||||
const [userProfile, setUserProfile] = useState<any>({});
|
||||
|
||||
// Ensure loading screen shows for at least 5 seconds
|
||||
useEffect(() => {
|
||||
@@ -108,6 +114,23 @@ export default function Home() {
|
||||
loadSharedPrayers();
|
||||
}, []);
|
||||
|
||||
// Load user profile
|
||||
useEffect(() => {
|
||||
const loadUserProfile = async () => {
|
||||
if (!user) return;
|
||||
try {
|
||||
const docRef = doc(db, 'users', user.uid);
|
||||
const docSnap = await getDoc(docRef);
|
||||
if (docSnap.exists()) {
|
||||
setUserProfile(docSnap.data());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading user profile:', error);
|
||||
}
|
||||
};
|
||||
loadUserProfile();
|
||||
}, [user]);
|
||||
|
||||
// Export functions
|
||||
const exportToPDF = (prayerList: Prayer[], title: string) => {
|
||||
const doc = new jsPDF();
|
||||
@@ -291,26 +314,37 @@ export default function Home() {
|
||||
const activePrayers = prayers.filter(prayer => !prayer.archived);
|
||||
const archivedPrayers = prayers.filter(prayer => prayer.archived);
|
||||
|
||||
// Filter prayers based on search term
|
||||
const filteredActivePrayers = activePrayers.filter(prayer =>
|
||||
prayer.text.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
prayer.journal.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
prayer.type.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
const filteredArchivedPrayers = archivedPrayers.filter(prayer =>
|
||||
prayer.text.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
prayer.journal.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
prayer.type.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
// Enhanced filtering function
|
||||
const applyFilters = (prayerList: Prayer[]) => {
|
||||
return prayerList.filter(prayer => {
|
||||
// Keyword search
|
||||
const matchesSearch = !searchTerm ||
|
||||
prayer.text.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
prayer.journal.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
prayer.type.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
prayer.prayFor.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
|
||||
// Date range filter
|
||||
const prayerDate = new Date(prayer.date);
|
||||
const fromDate = dateFrom ? new Date(dateFrom) : null;
|
||||
const toDateFilter = dateTo ? new Date(dateTo) : null;
|
||||
const matchesDateFrom = !fromDate || prayerDate >= fromDate;
|
||||
const matchesDateTo = !toDateFilter || prayerDate <= toDateFilter;
|
||||
|
||||
// Type filter
|
||||
const matchesType = typeFilter === 'all' || prayer.type === typeFilter;
|
||||
|
||||
return matchesSearch && matchesDateFrom && matchesDateTo && matchesType;
|
||||
});
|
||||
};
|
||||
|
||||
// Apply filters to each prayer list
|
||||
const filteredActivePrayers = applyFilters(activePrayers);
|
||||
const filteredArchivedPrayers = applyFilters(archivedPrayers);
|
||||
|
||||
// For search tab, combine all filtered prayers
|
||||
const allPrayers = [...activePrayers, ...archivedPrayers];
|
||||
const filteredSearchPrayers = allPrayers.filter(prayer =>
|
||||
prayer.text.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
prayer.journal.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
prayer.type.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
const filteredSearchPrayers = applyFilters(allPrayers);
|
||||
|
||||
// Pagination logic
|
||||
const totalActivePages = Math.ceil(filteredActivePrayers.length / itemsPerPage);
|
||||
@@ -422,6 +456,34 @@ export default function Home() {
|
||||
}
|
||||
};
|
||||
|
||||
// Mark prayer as answered/unanswered
|
||||
const toggleAnsweredPrayer = async (prayerId: string, answered: boolean) => {
|
||||
try {
|
||||
const updateData: any = { answered };
|
||||
if (answered) {
|
||||
updateData.answeredDate = new Date().toISOString().split('T')[0];
|
||||
} else {
|
||||
updateData.answeredDate = null;
|
||||
}
|
||||
|
||||
await updateDoc(doc(db, 'prayers', prayerId), updateData);
|
||||
|
||||
// Update local state
|
||||
setPrayers(prev => prev.map(p =>
|
||||
p.id === prayerId ? { ...p, answered, answeredDate: answered ? new Date().toISOString().split('T')[0] : undefined } : p
|
||||
));
|
||||
|
||||
// Send notification if answered and notifications are enabled
|
||||
if (answered && userProfile.notificationsEnabled) {
|
||||
// This would trigger a server-side notification via Firebase Functions
|
||||
console.log('Prayer answered - notification would be sent');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating prayer answered status:', error);
|
||||
alert('Failed to update prayer. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
// Start editing a prayer
|
||||
const startEditing = (prayer: Prayer) => {
|
||||
setEditingPrayer(prayer);
|
||||
@@ -686,23 +748,75 @@ export default function Home() {
|
||||
{activeTab !== 'add' && (
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{activeTab === 'search' && (
|
||||
<div className="mb-6">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search all prayers..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => {
|
||||
setSearchTerm(e.target.value);
|
||||
setCurrentPage(1); // Reset to first page when searching
|
||||
}}
|
||||
className="w-full p-3 bg-gray-800 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
autoFocus
|
||||
/>
|
||||
{searchTerm && (
|
||||
<p className="text-gray-400 text-sm mt-2">
|
||||
Found {filteredSearchPrayers.length} prayer{filteredSearchPrayers.length !== 1 ? 's' : ''}
|
||||
</p>
|
||||
)}
|
||||
<div className="mb-6 space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search keywords..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => {
|
||||
setSearchTerm(e.target.value);
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
className="p-3 bg-gray-800 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
|
||||
<select
|
||||
value={typeFilter}
|
||||
onChange={(e) => {
|
||||
setTypeFilter(e.target.value as 'all' | 'prayer' | 'praise');
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
className="p-3 bg-gray-800 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="all">All Types</option>
|
||||
<option value="prayer">Prayers Only</option>
|
||||
<option value="praise">Praises Only</option>
|
||||
</select>
|
||||
|
||||
<input
|
||||
type="date"
|
||||
placeholder="From date"
|
||||
value={dateFrom}
|
||||
onChange={(e) => {
|
||||
setDateFrom(e.target.value);
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
className="p-3 bg-gray-800 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
|
||||
<input
|
||||
type="date"
|
||||
placeholder="To date"
|
||||
value={dateTo}
|
||||
onChange={(e) => {
|
||||
setDateTo(e.target.value);
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
className="p-3 bg-gray-800 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<button
|
||||
onClick={() => {
|
||||
setSearchTerm('');
|
||||
setTypeFilter('all');
|
||||
setDateFrom('');
|
||||
setDateTo('');
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
className="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium rounded-lg"
|
||||
>
|
||||
Clear Filters
|
||||
</button>
|
||||
|
||||
{(searchTerm || typeFilter !== 'all' || dateFrom || dateTo) && (
|
||||
<p className="text-gray-400 text-sm">
|
||||
Found {filteredSearchPrayers.length} prayer{filteredSearchPrayers.length !== 1 ? 's' : ''}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -757,7 +871,26 @@ export default function Home() {
|
||||
</div>
|
||||
)}
|
||||
{activeTab !== 'search' && (
|
||||
<div className="flex justify-end mt-3 pt-2 border-t border-gray-600">
|
||||
<div className="flex justify-between items-center mt-3 pt-2 border-t border-gray-600">
|
||||
<div className="flex items-center gap-2">
|
||||
{prayer.answered && (
|
||||
<span className="text-green-400 text-sm font-medium flex items-center gap-1">
|
||||
✅ Answered {prayer.answeredDate && `on ${new Date(prayer.answeredDate).toLocaleDateString()}`}
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
onClick={() => toggleAnsweredPrayer(prayer.id, !prayer.answered)}
|
||||
className={`px-3 py-1 text-sm font-medium rounded transition-colors ${
|
||||
prayer.answered
|
||||
? 'text-yellow-500 hover:text-yellow-400 hover:bg-gray-700'
|
||||
: 'text-green-500 hover:text-green-400 hover:bg-gray-700'
|
||||
}`}
|
||||
title={prayer.answered ? 'Mark as unanswered' : 'Mark as answered'}
|
||||
>
|
||||
{prayer.answered ? '↩️ Unmark' : '✅ Mark Answered'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{prayer.archived ? (
|
||||
<button
|
||||
onClick={() => toggleArchivePrayer(prayer.id, false)}
|
||||
|
||||
Reference in new issue
Block a user