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>
  );
}