Main Model Documentation
Filepath :src\app\contexts\MainContext.tsx
"use client"
import { useContext, createContext, useState, ReactNode, useEffect } from "react";
import { toast } from "sonner";
import { useConsole } from "./AdminContext";
import { db } from "../firebase";
import { addDoc,doc, setDoc, updateDoc, deleteDoc,getDocs, collection, getDoc} from "firebase/firestore";
import { useAuth } from "./AuthContext";interface ChildProps {
children: ReactNode;
}const MainContext = createContext<any | undefined>(undefined);//Date factory
const today = new Date();
const formattedDate = today.toLocaleDateString();
let limits: { [key: string]: string } = {};
let incomeNames: { [key: string]: string } = {};
let incomeNamesArr: string[] = [];
let classes : { } = {};
let owing //incomeField specilized object's factory
if(school){
if (school["incomeField"]) {
const incomeField = school["incomeField"];
limits = incomeField.reduce((acc: any, key: any) => {
acc[key.incDesc] = key.limit;
return acc;
}, {});
incomeNames = incomeField.reduce((acc: any, key: any) => {
acc[key.incDesc] = "";
return acc;
}, {});
incomeNamesArr = Object.keys(incomeNames);
}
if (school["classField"]) {
const classField = school["classField"];
classes = Object.keys(classField.reduce((acc: any, key: any) => {
acc[key.className] = "";
return acc;
}, {}));
}
}
//Gender Factory
const female = learners.filter((learner:any)=> learner.gender ==="female")
const male = learners.filter((learner:any)=> learner.gender ==="male")//Utility functions
const skipProperties = ["firstname", "lastname", "period" , "level" ,"id"];
let total = payments.reduce((acc: any, obj: any) => {
return acc + Object.keys(obj)
.filter(key => !skipProperties.includes(key))
.reduce((sum, key) => {
return sum + (parseInt(obj[key]) || 0);
}, 0);
}, 0);
let totalOwings = owings.reduce((acc: any, obj: any) => {
return acc + Object.keys(obj)
.filter(key => !skipProperties.includes(key))
.reduce((sum, key) => {
return sum + (parseInt(obj[key]) || 0);
}, 0);
}, 0);
let totalsOwings = payments.reduce((acc: any, obj: any) => {
for (let key in obj) {
if (!skipProperties.includes(key)) {
acc[key] = (acc[key] || 0) + (parseInt(obj[key]) || 0);
}
}
return acc;
}, {});
let totals = payments.reduce((acc: any, obj: any) => {
for (let key in obj) {
if (!skipProperties.includes(key)) {
acc[key] = (acc[key] || 0) + (parseInt(obj[key]) || 0);
}
}
return acc;
}, {});
//learner management
const addLearner = async (newLearner: any) => {
const learnerExists = learners.some((learner: { id: any; }) => learner.id === newLearner.id);
if (learnerExists) {
toast.error("Learner already exists.");
return;
}
try {
await setDoc(doc(db, "learners", newLearner.id.toString()), newLearner);
toast("Learner added successfully");
await fetchLearners();
} catch (error) {
toast.error("Failed to add learner");
}
};
const fetchLearners = async () => {
try {
const learnersCollection = collection(db, "learners");
const learnersSnapshot = await getDocs(learnersCollection);
const learnersList = learnersSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
setLearners(learnersList);
} catch (error) {
toast.error("Failed to fetch learners");
}
};
useEffect(() => {
if (user) { // Check if the user is authenticated
fetchLearners();
fetchPayments();
fetchOwings();
fetchTransactions();
}
}, [user]);
const deleter = async (id: number) => {
const updatedLearners = learners.filter((learner: any) => learner.id !== id);
setLearners(updatedLearners);
try {
await deleteDoc(doc(db, "learners", id.toString()));
toast("Learner deleted successfully");
} catch (error) {
toast.error("Failed to delete learner");
}
};
//Payment management
const addPayment = async (newPayment: any) => {
let learnerExist = learners.find((learner: any) => (
learner.firstname === newPayment.firstname && learner.lastname === newPayment.lastname //&& learner.level === newPayment.level
));
let paymentExists = payments.find((payment: any) => (
payment.period === newPayment.period && payment.firstname === newPayment.firstname
));
try {
if (paymentExists) {
let hasValidNumericValue = false;
const updatedPayment = payments.map((payment: any) => {
if (payment.period === newPayment.period && payment.firstname === newPayment.firstname) {
return {
...payment,
...Object.keys(newPayment).reduce((acc: any, key) => {
if (key === "income") {
newPayment.income.forEach((incomeEntry: any) => {
if (incomeEntry.incomeDescription && incomeEntry.incomeAmount) {
const currentAmount = parseInt(payment[incomeEntry.incomeDescription]) || 0;
const newAmount = currentAmount + (parseInt(incomeEntry.incomeAmount) || 0);
const incomeLimit = parseInt(limits[incomeEntry.incomeDescription]) || 0;
if (currentAmount < incomeLimit) {
if (newAmount > incomeLimit) {
toast(`${incomeEntry.incomeDescription} exceeds the limit of ${incomeLimit}`);
} else {
acc[incomeEntry.incomeDescription] = newAmount;
hasValidNumericValue = true;
}
}
}
});
} else if (key !== "firstname" && key !== "lastname" && key !== "level" && key !== "period") {
const currentAmount = parseInt(payment[key]) || 0;
const newValue = currentAmount + (parseInt(newPayment[key]) || 0);
const limit = parseInt(limits[key]) || 0;
if (currentAmount < limit) {
if (newValue > limit) {
toast(`${key} exceeds the limit of ${limit}`);
} else {
acc[key] = newValue;
hasValidNumericValue = true;
}
}
} else {
acc[key] = newPayment[key];
}
return acc;
}, {})
};
}
return payment;
}).find((p: { period: any; firstname: any; }) => p.period === newPayment.period && p.firstname === newPayment.firstname); // Get the updated payment object
if (hasValidNumericValue) {
await updateDoc(doc(db, "payments", paymentExists.id), updatedPayment);
await fetchPayments();
toast("Payment updated");
calculateOwings(updatedPayment)
addtransaction(updatedPayment)
} else {
toast("No valid numeric values to update. Payment not modified.");
}
} else if (learnerExist) {
let hasValidNumericField = false;
const acc = Object.keys(newPayment).reduce((acc: any, key) => {
if (key === "income") {
newPayment.income.forEach((incomeEntry: any) => {
if (incomeEntry.incomeDescription && incomeEntry.incomeAmount) {
const incomeAmount = parseInt(incomeEntry.incomeAmount);
const incomeLimit = parseInt(limits[incomeEntry.incomeDescription]) || 0;
if (incomeAmount <= incomeLimit) {
acc[incomeEntry.incomeDescription] = incomeAmount;
hasValidNumericField = true;
} else {
toast(`${incomeEntry.incomeDescription} exceeds the limit of ${incomeLimit}`);
}
}
});
} else if (key !== "firstname" && key !== "lastname" && key !== "level" && key !== "period") {
const limit = limits[key];
const amount = parseInt(newPayment[key]) || 0;
if (amount < (parseInt(limit) || 0)) {
acc[key] = amount;
hasValidNumericField = true;
}
} else {
acc[key] = newPayment[key];
}
return acc;
}, {});
if (hasValidNumericField) {
await setDoc(doc(db, "payments", `${newPayment.firstname}-${newPayment.period}`), acc);
await fetchPayments();
toast("Payment added");
calculateOwings(acc);
addtransaction(acc);
}
} else {
toast("Learner does not exist");
}
} catch (error) {
toast.error("Failed to process payment");
}
};
//Owings Management
const calculateOwings = async (paymentMade: any) => {
console.log("Calculating owings for:", paymentMade); // Log the incoming payment data
try {
const owing = Object.keys(paymentMade).reduce((acc:any, key) => {
const owingstocalculate = Object.keys(limits)
if(owingstocalculate.includes(key)){
const paymentValue = parseInt(paymentMade[key]) || 0; // Default to 0 if the property doesn't exist
const limitValue = parseInt(limits[key]) || 0;
console.log(`Key: ${key}, Payment Value: ${paymentValue}, Limit: ${limitValue}`);
// Calculate the owing amount if paymentValue is less than limitValue
if (paymentValue < limitValue) {
acc[key] = limitValue - paymentValue;
}
}
return acc;
}, {});
const owingExists = Object.keys(owing).length > 0;
if (owingExists) {
// Add owing to Firestore
owing.firstname = paymentMade.firstname;
owing.lastname = paymentMade.lastname;
owing.level = paymentMade.level;
owing.period = paymentMade.period;
await setDoc(doc(db, "owings", `${paymentMade.firstname}-${paymentMade.period}`), owing);
await fetchOwings();
toast("Owings added");
} else {
// Check if the document exists and delete it if it does
const docRef = doc(db, "owings", `${paymentMade.firstname}-${paymentMade.period}`);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
await deleteDoc(docRef);
await fetchOwings();
console.log("Document successfully deleted!");
toast("Owings deleted");
}
}
} catch (error) {
console.error("Error processing owings:", error);
toast.error("Failed to process owing");
}
};
const addtransaction=async(newPayment:any)=>{
const date = new Date().toLocaleDateString();
newPayment.date = date;
await setDoc(doc(db, "transactions",`${newPayment.firstname}-${newPayment.period}`), newPayment);
await fetchTransactions()
}const fetchOwings = async () => {
try {
const owingsCollection = collection(db, "owings");
const owingsSnapshot = await getDocs(owingsCollection);
const owingsList = owingsSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
setOwings(owingsList);
} catch (error) {
toast.error("Failed to fetch payments");
}
};const fetchPayments = async () => {
try {
const paymentsCollection = collection(db, "payments");
const paymentsSnapshot = await getDocs(paymentsCollection);
const paymentsList = paymentsSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
setPayments(paymentsList);
} catch (error) {
toast.error("Failed to fetch payments");
}
};const fetchTransactions = async () => {
try {
const transactionsCollection = collection(db, "transactions");
const transactionsSnapshot = await getDocs(transactionsCollection);
const fetchedTransactions = transactionsSnapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
setTransactions(fetchedTransactions);
} catch (error) {
console.error("Error fetching transactions:", error);
toast.error("Failed to fetch transactions");
}
};export const useMain = () => {
return useContext(MainContext);
}
export const MainProvider = ({ children }: ChildProps) => {
const {user} = useAuth()
const { school } = useConsole();
const [learners, setLearners] = useState<any>([]);
const [payments, setPayments] = useState<any>([]);
const [owings ,setOwings] =useState<any>([])
const [transactions , setTransactions] =useState<any>([])
const values = {
addLearner,
addPayment,
learners,
deleter,
payments,
incomeNames,
incomeNamesArr,
classes,
female,
male,
total,
totals,
formattedDate,
totalOwings,
owings,
transactions
};
return (
<MainContext.Provider value={values}>
{children}
</MainContext.Provider>
);
}