diff --git a/app/(tabs)/facility.tsx b/app/(tabs)/facility.tsx index baf9284..249dcb7 100644 --- a/app/(tabs)/facility.tsx +++ b/app/(tabs)/facility.tsx @@ -1,354 +1,296 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect } from "react"; import { View, Text, TouchableOpacity, - StyleSheet, - TextInput, FlatList, - ScrollView, + StyleSheet, Alert, -} from 'react-native'; -import { Picker } from '@react-native-picker/picker'; -import DateTimePicker from '@react-native-community/datetimepicker'; -import { db } from '../../scripts/firebase'; + SafeAreaView, +} from "react-native"; +import { useNavigation } from "@react-navigation/native"; +import { Ionicons } from "@expo/vector-icons"; +import { db } from "../../scripts/firebase"; import { collection, - addDoc, - onSnapshot, - query, - orderBy, doc, + getDocs, + addDoc, + updateDoc, deleteDoc, -} from 'firebase/firestore'; -import tw from 'twrnc'; -import { ArrowLeft } from 'lucide-react-native'; -import { useNavigation } from '@react-navigation/native'; + getDoc, + query, + where, + onSnapshot, +} from "firebase/firestore"; + +type Pass = { + id?: string; + facilityId: string; + facilityName: string; + date: string; + time: string; + expiresAt: string; + count: string; +}; -const FacilityBookingPage = () => { - const [facility, setFacility] = useState(''); - const [date, setDate] = useState(new Date()); - const [time, setTime] = useState(new Date()); - const [count, setCount] = useState(''); - const [showDatePicker, setShowDatePicker] = useState(false); - const [showTimePicker, setShowTimePicker] = useState(false); - const [passes, setPasses] = useState([]); +const FacilitiesBooking = () => { const navigation = useNavigation(); + const [facilities, setFacilities] = useState([]); + const [selectedFacility, setSelectedFacility] = useState(null); + const [availableSlots, setAvailableSlots] = useState(0); + const [passes, setPasses] = useState([]); + const [currentDate, setCurrentDate] = useState( + new Date().toISOString().split("T")[0] + ); + const userId = "currentUserId"; useEffect(() => { - const passesCollection = collection(db, 'passes'); - const q = query(passesCollection, orderBy('createdAt', 'desc')); - - const unsubscribe = onSnapshot(q, (querySnapshot) => { - const fetchedPasses = querySnapshot.docs.map((doc) => ({ + const fetchFacilities = async () => { + const facilitiesSnapshot = await getDocs(collection(db, "Facilities")); + const facilitiesData = facilitiesSnapshot.docs.map((doc) => ({ id: doc.id, ...doc.data(), })); - setPasses(fetchedPasses); - }); + setFacilities(facilitiesData); + }; + + const fetchPasses = () => { + const passesRef = collection(db, "passes"); + const q = query(passesRef, where("userId", "==", userId)); + + const unsubscribe = onSnapshot(q, (snapshot) => { + const passesData = snapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })) as Pass[]; + setPasses(passesData); + }); + + return unsubscribe; + }; + + fetchFacilities(); + const unsubscribePasses = fetchPasses(); - return () => unsubscribe(); + return () => unsubscribePasses(); }, []); - const generatePass = async () => { - if (!facility || !count) { - alert('Please select a facility and enter the number of people.'); + const bookSlot = async () => { + if (!selectedFacility) { + Alert.alert("Error", "Please select a facility."); return; } - - const selectedDateTime = new Date(date); - selectedDateTime.setHours(time.getHours()); - selectedDateTime.setMinutes(time.getMinutes()); - selectedDateTime.setSeconds(0); - selectedDateTime.setMilliseconds(0); - - const currentTime = new Date(); - const thirtyMinutesAhead = new Date(currentTime.getTime() + 30 * 60 * 1000); - - if (selectedDateTime <= thirtyMinutesAhead) { - alert('Pass can only be generated at least 30 minutes before the scheduled time.'); + + if (availableSlots <= 0) { + Alert.alert("Error", "No slots available for this facility."); return; } - - const expiration = new Date(selectedDateTime); - expiration.setHours(expiration.getHours() + 1); // Set the expiration time as 1 hour after the selected time - - const newPass = { - facility, - date: date.toDateString(), - time: time.toLocaleTimeString(), - count, - expiresAt: expiration.toISOString(), - createdAt: currentTime.toISOString(), + + const currentTime = new Date(); + const expirationTime = new Date( + currentTime.getTime() + 24 * 60 * 60 * 1000 + ); + + const newPass: Omit = { + facilityId: selectedFacility, + facilityName: + facilities.find((f) => f.id === selectedFacility)?.name || "", + date: currentDate, + time: currentTime.toLocaleTimeString(), + expiresAt: expirationTime.toISOString(), + count: "1", + userId, }; - + try { - const passesCollection = collection(db, 'passes'); - await addDoc(passesCollection, newPass); - alert('Pass generated successfully!'); + await addDoc(collection(db, "passes"), newPass); + const facilityRef = doc(db, "Facilities", selectedFacility); + await updateDoc(facilityRef, { + allocatedSlots: + facilities.find((f) => f.id === selectedFacility)?.allocatedSlots! + 1, + }); + Alert.alert("Success", "Slot booked successfully!"); } catch (error) { - console.error('Error adding pass: ', error); - alert('Failed to generate pass.'); + console.error("Error booking slot: ", error); + Alert.alert("Error", "Failed to book slot. Please try again."); } }; - - - const onChangeDate = (event, selectedDate) => { - const currentDate = selectedDate || date; - setShowDatePicker(false); - setDate(currentDate); - }; - const onChangeTime = (event, selectedTime) => { - const currentTime = selectedTime || time; - setShowTimePicker(false); - setTime(currentTime); - }; + const cancelPass = async (passId: string, facilityId: string) => { + try { + await deleteDoc(doc(db, "passes", passId)); + const facilityRef = doc(db, "Facilities", facilityId); + const facilitySnapshot = await getDoc(facilityRef); - const cancelPass = async (id, scheduledTime) => { - const currentTime = new Date(); - const scheduledDateTime = new Date(scheduledTime); - const thirtyMinutesBeforeScheduled = new Date(scheduledDateTime.getTime() - 30 * 60 * 1000); // 30 minutes before the scheduled time - - if (currentTime >= thirtyMinutesBeforeScheduled) { - alert('You can only cancel the pass at least 30 minutes before the scheduled time.'); - return; + if (facilitySnapshot.exists()) { + const facilityData = facilitySnapshot.data(); + await updateDoc(facilityRef, { + allocatedSlots: (facilityData.allocatedSlots || 1) - 1, + }); + Alert.alert("Success", "Pass cancelled successfully!"); + } + } catch (error) { + console.error("Error cancelling pass: ", error); + Alert.alert("Error", "Failed to cancel pass. Please try again."); } - - Alert.alert( - "Confirm Cancellation", - "Are you sure you want to cancel this pass?", - [ - { text: "Cancel", style: "cancel" }, - { - text: "Yes", - onPress: async () => { - try { - const passDoc = doc(db, 'passes', id); - await deleteDoc(passDoc); - alert('Pass canceled successfully!'); - } catch (error) { - console.error('Error canceling pass: ', error); - alert('Failed to cancel pass.'); - } - }, - }, - ] - ); - }; - - - const renderPass = ({ item }) => { - const currentTime = new Date(); - const expiresAt = new Date(item.expiresAt); - - const isExpired = currentTime.getTime() >= expiresAt.getTime(); - - if (isExpired) return null; // Don't render expired passes - - return ( - - {item.facility} - Date: {item.date} - Time: {item.time} - People: {item.count} - Expires At: {expiresAt.toLocaleTimeString()} - cancelPass(item.id, item.expiresAt)} - > - Cancel - - - ); }; return ( - - - navigation.navigate('home')} - style={tw`mr-4`} - > - - - Facilities - - - - - Select Facility: - setFacility(itemValue)} - style={styles.picker} - > - - - - - - - - - - setCount(text)} - /> - + + + setShowDatePicker(true)} + style={styles.backButton} + onPress={() => navigation.goBack()} > - - Choose Date: {date.toDateString()} - + + Facilities Booking + - setShowTimePicker(true)} - > - - Choose Time: {time.toLocaleTimeString()} - - - - {showDatePicker && ( - - )} - - {showTimePicker && ( - + item.id} + renderItem={({ item }) => ( + { + setSelectedFacility(item.id); + setAvailableSlots(item.totalSlots - item.allocatedSlots); + }} + > + {item.name} + + Slots Available: {item.totalSlots - item.allocatedSlots} / {item.totalSlots} + + )} + /> - - Generate Pass - - + {selectedFacility && ( + + + Selected Facility: {facilities.find((f) => f.id === selectedFacility)?.name} + + + Book Slot + + + )} - - Your Passes - item.id} - renderItem={renderPass} - /> - - - + Your Passes + item.id!} + renderItem={({ item }) => ( + + Facility: {item.facilityName} + Date: {item.date} + Time: {item.time} + + Expires At: {new Date(item.expiresAt).toLocaleString()} + + cancelPass(item.id!, item.facilityId)} + > + Cancel + + + )} + /> + + ); }; const styles = StyleSheet.create({ + safeArea: { + flex: 1, + backgroundColor: "white", + }, container: { flex: 1, - backgroundColor: '#f9f9f9', + backgroundColor: "white", }, - formContainer: { - backgroundColor: 'white', - borderRadius: 10, - padding: 20, - margin: 20, - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.25, - shadowRadius: 3.84, - elevation: 5, + headerContainer: { + flexDirection: "row", + alignItems: "center", + backgroundColor: "#000066", + paddingVertical: 30, + paddingHorizontal: 15, }, - label: { - fontSize: 16, - marginBottom: 10, - color: '#333', + backButton: { + marginRight: 10, }, - picker: { - height: 50, - marginBottom: 20, - borderWidth: 1, - borderColor: '#ccc', - borderRadius: 5, - backgroundColor: '#f0f0f0', + header: { + fontSize: 24, + fontWeight: "bold", + color: "white", }, - textInput: { - borderWidth: 1, - borderColor: '#ccc', - borderRadius: 5, - padding: 10, - marginBottom: 20, - backgroundColor: '#f0f0f0', + facilitiesList: { + paddingTop: 20, }, - button: { - backgroundColor: '#00215E', + facilityButton: { + backgroundColor: "#000066", padding: 15, - borderRadius: 5, - alignItems: 'center', - marginTop: 10, + borderRadius: 10, + marginBottom: 10, + marginHorizontal: 20, }, - buttonText: { - color: 'white', - fontSize: 16, + facilityText: { + fontSize: 18, + color: "white", }, - passesContainer: { - marginHorizontal: 20, - marginBottom: 20, + facilitySlots: { + fontSize: 14, + color: "#d0e4ff", }, - sectionTitle: { + selectedFacility: { fontSize: 18, - fontWeight: 'bold', - marginBottom: 10, - color: '#333', + marginVertical: 10, + color: "#000066", + textAlign: "center", }, - passCard: { - backgroundColor: 'white', - borderRadius: 10, - padding: 15, - marginBottom: 10, - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.25, - shadowRadius: 3.84, - elevation: 5, + bookButton: { + backgroundColor: "#000066", + padding: 10, + borderRadius: 5, + alignItems: "center", + margin: 20, }, - passCardTitle: { + bookText: { + color: "white", fontSize: 16, - fontWeight: 'bold', - color: '#00215E', - marginBottom: 5, }, - passCardDetail: { - fontSize: 14, - color: '#555', - marginBottom: 3, + passesHeader: { + fontSize: 20, + fontWeight: "bold", + marginVertical: 10, + color: "##000066", + textAlign: "center", }, - cancelButton: { - backgroundColor: '#FFA500', + passItem: { + backgroundColor: "#d0e4ff", padding: 10, + borderRadius: 10, + marginBottom: 10, + marginHorizontal: 20, + }, + cancelButton: { + backgroundColor: "#ff4d4d", + padding: 5, borderRadius: 5, - alignItems: 'center', - marginTop: 10, + marginTop: 5, }, - cancelButtonText: { - color: 'white', + cancelText: { + color: "white", fontSize: 14, + textAlign: "center", }, }); -export default FacilityBookingPage; \ No newline at end of file + +export default FacilitiesBooking; diff --git a/app/(tabs)/help.tsx b/app/(tabs)/help.tsx index d85b5e5..f3e3716 100644 --- a/app/(tabs)/help.tsx +++ b/app/(tabs)/help.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { View, Text, TouchableOpacity, ScrollView, SafeAreaView, Linking } from 'react-native'; +import React, { useState } from 'react'; +import { View, Text, TouchableOpacity, ScrollView, SafeAreaView, Modal } from 'react-native'; import tw from 'twrnc'; import { Ionicons } from '@expo/vector-icons'; import { ArrowLeft } from 'lucide-react-native'; @@ -7,34 +7,54 @@ import { useNavigation } from '@react-navigation/native'; const HelpAndSupportPage = () => { const navigation = useNavigation(); + const [modalVisible, setModalVisible] = useState(false); + const [modalContent, setModalContent] = useState(''); const supportOptions = [ { id: 1, title: 'FAQ', icon: 'help-circle-outline', - onPress: () => Linking.openURL('https://www.example.com/faq'), + content: `Frequently Asked Questions +1. How do I book a facility? + - Go to the "Facilities" section, select a facility, and click "Book Slot." + +2. What is the duration of a booked slot? + - Slots are generally valid for 1 day. + +3. How can I cancel a booking? + - Navigate to "My Bookings," select the booking, and click "Cancel." + +4. How do I reset my password? + - Go to the login page, click "Forgot Password," and follow the steps to reset your password. + +5. What should I do if I face an issue? + - Contact us via the "Contact Us" option in the Help & Support section.`, }, { id: 2, title: 'Contact Us', icon: 'mail-outline', - onPress: () => Linking.openURL('mailto:support@example.com'), + content: 'You can contact us via email at servelinkuser404@gmail.com.', }, { id: 3, title: 'User Guide', icon: 'book-outline', - onPress: () => Linking.openURL('https://www.example.com/user-guide'), - }, - { - id: 4, - title: 'Submit Feedback', - icon: 'chatbox-outline', - onPress: () => Linking.openURL('https://www.example.com/feedback'), + content: 'This is the user guide to help you navigate our app. 1. Home Page Access key features like notifications, announcements, and navigation options. 2. Facilities BookingView and book slots for amenities directly.Cancel bookings from "My Bookings."3. Guest Acces Add guest details to generate a QR code/OTP. Guests scan the QR code or enter the OTP at entry points.4. Parcel Management Track and confirm parcel deliveries.5. Maintenance RequestsSubmit and track maintenance requests (e.g., plumbing, electrical).6. Emergency AlertsnSend real-time emergency notifications with your location.7. Profile Management Update profile info and set app preferences.8. Notifications Stay updated on bookings, guests, and other alerts.9. Help & Support For assistance, visit the "Help & Support" section or contact us via email.', }, ]; + const openModal = (content) => { + setModalContent(content); + setModalVisible(true); + }; + + const closeModal = () => { + setModalVisible(false); + setModalContent(''); + }; + return ( {/* Header */} @@ -51,7 +71,7 @@ const HelpAndSupportPage = () => { openModal(option.content)} > {option.title} @@ -59,8 +79,31 @@ const HelpAndSupportPage = () => { ))} + + {/* Modal */} + + + + Help & Support + + {modalContent} + + + Close + + + + ); }; -export default HelpAndSupportPage; \ No newline at end of file +export default HelpAndSupportPage; diff --git a/package-lock.json b/package-lock.json index 1a2ae92..9630331 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,11 +14,12 @@ "@react-native-async-storage/async-storage": "^2.0.0", "@react-native-community/datetimepicker": "^8.2.0", "@react-native-firebase/messaging": "^21.6.1", - "@react-native-picker/picker": "^2.8.1", + "@react-native-picker/picker": "^2.10.2", "@react-navigation/native": "^6.1.18", "appwrite": "^16.1.0", "axios": "^1.7.9", "cloudinary-react-native": "^1.0.1", + "date-fns": "^4.1.0", "expo": "~51.0.28", "expo-av": "^14.0.7", "expo-blur": "^13.0.2", @@ -48,6 +49,7 @@ "react-native-animatable": "^1.4.0", "react-native-appwrite": "^0.5.0", "react-native-bcrypt": "^2.4.0", + "react-native-date-picker": "^5.0.8", "react-native-gesture-handler": "~2.16.1", "react-native-image-picker": "^7.2.2", "react-native-nfc-manager": "^3.16.0", @@ -7620,9 +7622,9 @@ } }, "node_modules/@react-native-picker/picker": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.8.1.tgz", - "integrity": "sha512-iFhsKQzRh/z3GlmvJWSjJJ4333FdLE/PhXxlGlYllE7sFf+UTzziVY+ajatuJ+R5zDw2AxfJV4v/3tAzUJb0/A==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.10.2.tgz", + "integrity": "sha512-kr3OvCRwTYjR/OKlb52k4xmQVU7dPRIALqpyiihexdJxEgvc1smnepgqCeM9oXmNSG4YaV5/RSxFlLC5Z/T/Eg==", "peerDependencies": { "react": "*", "react-native": "*" @@ -13142,6 +13144,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", @@ -24827,6 +24838,15 @@ "resolved": "https://registry.npmjs.org/react-native-bcrypt/-/react-native-bcrypt-2.4.0.tgz", "integrity": "sha512-nC8SR/YCCLIluxd1YEkUX2/xjKELLkO0pgIZc1JGUw9o+wnsfpujm0J8NSPmPEqFUe1n2EkysHnAmuKInDusoQ==" }, + "node_modules/react-native-date-picker": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/react-native-date-picker/-/react-native-date-picker-5.0.8.tgz", + "integrity": "sha512-1l4w4+iLuhOZUSyQrt5DclXnNx3RwEyp+wUrSaBfaHr/3FWtRWjLjS0E9FL4FWuq6vn09Hy9hn8lZJ1DmPc9YQ==", + "peerDependencies": { + "react": ">= 17.0.1", + "react-native": ">= 0.64.3" + } + }, "node_modules/react-native-gesture-handler": { "version": "2.16.2", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.16.2.tgz", diff --git a/package.json b/package.json index 8dae131..cb2f0eb 100644 --- a/package.json +++ b/package.json @@ -21,11 +21,12 @@ "@react-native-async-storage/async-storage": "^2.0.0", "@react-native-community/datetimepicker": "^8.2.0", "@react-native-firebase/messaging": "^21.6.1", - "@react-native-picker/picker": "^2.8.1", + "@react-native-picker/picker": "^2.10.2", "@react-navigation/native": "^6.1.18", "appwrite": "^16.1.0", "axios": "^1.7.9", "cloudinary-react-native": "^1.0.1", + "date-fns": "^4.1.0", "expo": "~51.0.28", "expo-av": "^14.0.7", "expo-blur": "^13.0.2", @@ -55,6 +56,7 @@ "react-native-animatable": "^1.4.0", "react-native-appwrite": "^0.5.0", "react-native-bcrypt": "^2.4.0", + "react-native-date-picker": "^5.0.8", "react-native-gesture-handler": "~2.16.1", "react-native-image-picker": "^7.2.2", "react-native-nfc-manager": "^3.16.0",