import {Timestamp} from "firebase/firestore"; import {Address, CohortData, InstituteData, OrganisationAddress, Products, ProviderUserPermissions, StaffUserPermissions, UserData} from "../typeDefinitions"; import * as geoHash from "ngeohash"; import FirebaseQuery from "./firebaseQuery"; import {getDownloadURL, ref} from "firebase/storage"; import {storage} from "./firebaseConfig"; export const getProviderId = async () => {/* let user = await getDoc(doc(db, 'users', auth.currentUser.uid)) return user.data().bi*/ }; export const getRandomNumber = (min = 0, max:number) => { return Math.random() * (max - min) + min; }; export const getUniqueId = (object:object): number => { const id = Math.round(getRandomNumber(0, 1000000)); if (!object) { return id; } if (Array.isArray(object)) { return object.some((el) => el.id === id) ? getUniqueId(object) : id; } return (Object.keys(object) as any[]).some((el) => el === id) ? getUniqueId(object) : id; }; export const capitalise = (s="") => { const nS = s.toString().replace("_", " "); return nS.charAt(0).toUpperCase() + nS.slice(1); }; export const capitaliseWords = (s="") => { return s.replace(/(^|\s)\S/g, (l) => l.toUpperCase()); }; export const snakeCase = (s="") => { const nS = s.toString().replace(" ", "_"); return nS.toLowerCase(); }; export const validateEmail = (email: string) => { console.log(email); return email.match( /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ ); }; export const getAccess = (user:UserData, ...perms: (keyof StaffUserPermissions | keyof ProviderUserPermissions)[]) => { // If the user has the permission given, grant access const finalPerms: {[key: string]: any} = {}; perms.forEach((perm) => { finalPerms[perm] = (user.userGroup === "admin" || (user.groupData as object)?.[perm] || false); }); // Otherwise, deny access return Object.keys(finalPerms).length > 1 ? finalPerms : Object.values(finalPerms)[0]; }; export const dateToString = (date:Date) => { const y = date.getFullYear(); const m = ("0"+(date.getMonth() + 1)).slice(-2); const d = ("0"+(date.getDate())).slice(-2); return [y, m, d].join("-"); }; export const getPlacementDateArray = (start:string, end:string) => { const dates = [start]; let currentDate = start; while (currentDate !== end) { const dateObj = new Date(currentDate); dateObj.setDate(dateObj.getDate()+1); const dateObjString = dateToString(dateObj); dates.push(dateObjString); currentDate = dateObjString; } return dates; }; export const reformatDate = (date: Date|string) => { if (!date) { return; } return date.toString().split("-").reverse().join("."); }; export const convertDate = (date?: string|Date, output: "dbstring"|"date"|"visual" = "date") => { // dbstring = yyyy-MM-dd // visual = dd MMM yyyy // date = Date if (!date) return; let year:number; let month:number; let day:number; const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; if (typeof date === "string") { const dateType = date.includes(" ") ? "visual" : "dbstring"; const dateArray = date.split(dateType === "visual" ? " " : "-"); year = parseInt(dateArray[dateType === "visual" ? 2 : 0]); month = dateType === "visual" ? months.indexOf(dateArray[1]) : parseInt(dateArray[1]) - 1; day = parseInt(dateArray[dateType === "visual" ? 0 : 2]); } else { year = date.getFullYear(); month = date.getMonth(); day = date.getDate(); } switch (output) { case "date": return new Date(year, month, day); case "dbstring": return [year, month+1 < 10 ? `0${month+1}` : month+1, day < 10 ? `0${day}` : day].join("-"); case "visual": return [day, months[month], year].join(" "); } }; export const getDateDiff = (start: Date, end:Date): number => { return (Math.round((new Date(dateToString(end)).getTime() - new Date(dateToString(start)).getTime()) / (1000 * 60 * 60 * 24))); }; export const reformatDateTime = (timestamp: string|Timestamp) => { if (typeof timestamp === "string") { return `${timestamp.split("T")[1].substring(0, 8)} ${(timestamp.split("T")[0]).split("-").reverse().join(".")}`; } try { const created = new Date(timestamp.seconds * 1000).toISOString(); return `${created.split("T")[1].substring(0, 8)} ${(created.split("T")[0]).split("-").reverse().join(".")}`; } catch (e) { return null; } }; export function stringToDate(date: string) { const formatItems= date.split("/").map((i) => parseInt(i)); console.log("date array", formatItems); return new Date(formatItems[2], formatItems[1]-1, formatItems[0]); } // export const editNestedObject = (loc:(string|number)[], data:any[]|{[key: string|number]: unknown}, value:any, objectId=false) => { // if (!data || !(Array.isArray(data) || typeof data === "object")) { // console.error("Error editing nested JSON. Invalid type"); // return; // } // type IndexType = typeof data extends any[] ? number : number|string // const prevData: typeof data = Array.isArray(data) ? [...data] : {...data}; // const locationIndex:IndexType = objectId && Array.isArray(data) ? (prevData as any[]).findIndex((x) => x.id === loc[0]) : loc[0]; // if (loc.length > 1) { // prevData[locationIndex as any] = editNestedObject(loc.slice(1), data[locationIndex as any] || {}, value, objectId); // } else { // if (value === undefined) { // Array.isArray(prevData) ? prevData.splice(locationIndex as number, 1) : delete prevData[locationIndex]; // } else { // prevData[locationIndex as any] = value; // } // } // return prevData; // }; export const editNestedObject = ( loc: (string | number)[], data: any[] | { [key: string | number]: unknown }, value: any, objectId = false, append = false // New parameter to enable appending ) => { if (!data || !(Array.isArray(data) || typeof data === "object")) { console.error("Error editing nested JSON. Invalid type"); return; } type IndexType = typeof data extends any[] ? number : number | string; const prevData: typeof data = Array.isArray(data) ? [...data] : { ...data }; const locationIndex: IndexType = objectId && Array.isArray(data) ? (prevData as any[]).findIndex((x) => x.id === loc[0]) : loc[0]; if (loc.length > 1) { prevData[locationIndex as any] = editNestedObject( loc.slice(1), data[locationIndex as any] || (append ? [] : {}), value, objectId, append ); } else { if (value === undefined) { Array.isArray(prevData) ? prevData.splice(locationIndex as number, 1) : delete prevData[locationIndex]; } else if (append && Array.isArray(prevData[locationIndex as any])) { (prevData[locationIndex as any] as any[]).push(value); // Append instead of replace } else { prevData[locationIndex as any] = value; } } return prevData; }; export const getItemInNestedObject = (path:string[], object) => { if (path.length === 0) return; const item = object?.[path.shift() as string]; if (item === undefined) return item; if (path.length > 0) { return getItemInNestedObject(path, item); } return item; }; export const camelCaseToNormal = (string="") => { return string.replace(/([A-Z])/g, " $1").toLowerCase().trim().replace(/^./, function(str) { return str.toUpperCase(); }); }; export const camelCase = (string="") => { return string.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase()); }; export const isJson = (item:any) => { item = typeof item !== "string" ? JSON.stringify(item) : item; try { item = JSON.parse(item); } catch (e) { return false; } return (typeof item === "object" && item !== null); }; export const sortByReverseStringLength = (array: string[]) => { return array.sort(function(a, b) { return b.length - a.length; }); }; export const pathToArr = (path: any[]|any) => Array.isArray(path) ? path : [path]; export const arraysEqual = (a:any[], b:any[]) => { if (a === b) return true; if (a == null || b == null) return false; if (a.length !== b.length) return false; return a.every((val, idx) => val === b[idx]); }; export const objectsEqual = (x:object, y:object):boolean => { const ok = Object.keys; const tx = typeof x; const ty = typeof y; return x && y && tx === "object" && tx === ty ? ( ok(x).length === ok(y).length && ok(x).every((key) => objectsEqual(x[key as keyof typeof x], y[key as keyof typeof x])) ) : (x === y); }; export const arrayUniqueValues = (array:unknown[]) => { return array.filter((value, index, self) => self.indexOf(value) === index); }; export const flattenObject = (obj) => { const flattened = {}; Object.keys(obj).forEach((key) => { const value = obj[key]; if (typeof value === "object" && value !== null && !Array.isArray(value)) { Object.assign(flattened, flattenObject(value)); } else { flattened[key] = value; } }); return flattened; }; export const getGeoHash = async (data:Partial
) => { if (!data?.["address-line1"] || !data.postal_code) return; const address = data?.["address-line1"] + " " + data?.["address-line2"] + " " + data?.locality + " " + data?.postal_code + " " + data?.country; address.replace(" ", "+"); const apiQuery = "https://maps.googleapis.com/maps/api/geocode/json?address=" + address + "&key=AIzaSyBOSSi4iHxOoAS9tLAJUAC_46HlZ6-D5Ss"; const location = await fetch(apiQuery) .then(async (res) => await res.json() as {results: {geometry: {location: unknown}}[]}) .then((res: {results: {geometry: {location: unknown}}[]}) => { return res; }, (error: Error) => { return error; }); if (location instanceof Error || !location.results.length) { return; } const result = location.results[0].geometry.location as {lat:number, lng:number}; return geoHash.encode(result.lat, result.lng, 12); }; export const decodeGeoHash = (g: string) => { console.log("g", g); if (!g) return; return geoHash.decode(g); }; export const randomPassword = () => { let text = ""; const c = "ABCDEFGHIJKLMNNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz0123456789"; for (let i = 0; i < 12; i++) { text += c.charAt(Math.floor(Math.random() * c.length)); } return text; }; export const arrayEquals = (a:unknown, b:unknown) => { return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((val, index) => val === b[index]); }; export const average = (arr: number[]) => arr.reduce( ( p, c ) => p + c, 0 ) / arr.length; export const readingTime = (text: string) => { if (!text) return; const wpm = 225; const words = text.trim().split(/\s+/).length; const time = Math.ceil(words / wpm); return `${time} minute${(time > 1) ? "s" : ""}`; }; /* export const imageToPdf = async (image: File):Promise => { const doc = PDFDocument; const imgStream = await image.arrayBuffer(); doc.image(imgStream, { fit: [500, 400], align: 'center', valign: 'center' }); doc.end(); // will trigger the stream to end return await pdfToBase64(doc); } function pdfToBase64(doc): Promise { return new Promise((resolve, reject) => { // Create a buffer const buffers: Buffer[] = []; doc.on('data', (chunk: Buffer) => { buffers.push(chunk); }); doc.on('end', () => { // Concatenate all the buffers to form a single buffer const pdfBuffer = Buffer.concat(buffers); // Convert the buffer to a base64 string const base64String = pdfBuffer.toString('base64'); resolve(base64String); }); // Handle errors doc.on('error', (error: Error) => { reject(error); }); // End the document doc.end(); }); } */ export function getMaximumStudentsFromListingCohorts(cohorts?: {[key: string]: { startDate?: string, studentPlacements?: string[] }}): number { const today = new Date(); const threeMonthsAgo = new Date(); threeMonthsAgo.setMonth(today.getMonth() - 3); if (!cohorts) return 0; // Convert object values to an array const cohortArray = Object.values(cohorts); // Filter out cohorts within the last 3 months or in the future const validCohorts = cohortArray.filter((cohort) => cohort.startDate ? new Date(cohort.startDate) < threeMonthsAgo : false ); if (validCohorts.length > 0) { // Sort by startDate descending (most recent first) validCohorts.sort((a, b) => new Date(b.startDate!) .getTime() - new Date(a.startDate!).getTime()); return validCohorts[0].studentPlacements?.length || 0; } return Math.max(...cohortArray.map((c) => c.studentPlacements?.length || 0), 0); } export const getImageColorInstituteForEmail = async (product: Products, oId: string, cohort?: string, schoolId?: string):Promise< {image?: string, color?: string, name?: string, email?: string, package?: string}> => { const firebaseQuery = new FirebaseQuery(); if (product !== "institutes") return {}; const oIdRef = ref(storage, `${product}/${oId}/profilePic.png`); const oIdImage = await getDownloadURL(oIdRef).catch(() => undefined); const institute = await firebaseQuery.getDocData(["institutes", oId]) as InstituteData; if (!cohort && !schoolId) { const adminUser = await firebaseQuery.getDocData(["users", institute.admin]) as UserData; return { image: oIdImage, name: institute.name, email: adminUser.email, color: institute.color, package: institute.package, }; } const cohortData = cohort ? (await firebaseQuery.getDocData(["cohorts", cohort]) as CohortData) : undefined; const mSchoolId = schoolId || cohortData?.schoolId; const adminUser = await firebaseQuery.getDocData(["users", cohortData?.designatedStaff || institute.admin]) as UserData; if (!mSchoolId) { return { image: oIdImage, name: institute.name, color: institute.color, email: adminUser.email, }; } const schoolData = await firebaseQuery.getDocData(["schools", mSchoolId]) as OrganisationAddress; const instituteName = schoolData.name ||institute.name; const cohortRef = ref(storage, `${product}/${oId}/${mSchoolId}.png`); const cohortImage = await getDownloadURL(cohortRef).catch(() => undefined); return { image: cohortImage || oIdImage, color: schoolData.color, name: instituteName, email: adminUser.email, }; }; export const quoteAlgoliaFilterIfNeeded = (v: any) => /\s|:|"/.test(v) ? `'${v.replace(/'/g, "\\'")}'` : v;