diff --git a/.firebasesrc b/.firebasesrc new file mode 100644 index 000000000..005161658 --- /dev/null +++ b/.firebasesrc @@ -0,0 +1,9 @@ +{ + "projects": { + "default": "delib-v3-dev", + "dev": "delib-v3-dev", + "prod": "synthesistalyaron" + }, + "targets": {}, + "etags": {} +} diff --git a/src/assets/Languages/he.json b/src/assets/Languages/he.json index 296a0657b..9c5fadf21 100644 --- a/src/assets/Languages/he.json +++ b/src/assets/Languages/he.json @@ -174,4 +174,5 @@ "Support": "תומכים", "Against": "מתנגדים", "Voters": "מצביעים" -} \ No newline at end of file + "Did Not Vote": "לא הצביעו", +} diff --git a/src/controllers/db/configKey.ts b/src/controllers/db/configKey.ts index 47a1e2d60..02251b0b7 100644 --- a/src/controllers/db/configKey.ts +++ b/src/controllers/db/configKey.ts @@ -1,6 +1,6 @@ const mode = import.meta.env.VITE_APP_ENV as 'development' | 'production'; - +console.log("mode", mode); const firebaseConfig = { development: { apiKey: import.meta.env.VITE_FIREBASE_API_KEY_DEV, @@ -24,7 +24,6 @@ const firebaseConfig = { }, }; - const vapidKeys = { development: import.meta.env.VITE_FIREBASE_VAPID_KEY_DEV, production: import.meta.env.VITE_FIREBASE_VAPID_KEY_PROD, diff --git a/src/controllers/db/vote/getVotes.ts b/src/controllers/db/vote/getVotes.ts index 376efeeae..164211eec 100644 --- a/src/controllers/db/vote/getVotes.ts +++ b/src/controllers/db/vote/getVotes.ts @@ -1,10 +1,10 @@ import { - collection, - doc, - getDoc, - getDocs, - query, - where, + collection, + doc, + getDoc, + getDocs, + query, + where, } from "firebase/firestore"; import { Collections, Statement, StatementSchema, Vote } from "delib-npm"; import { DB } from "../config"; @@ -14,56 +14,52 @@ import { store } from "@/model/store"; // Why get user from firebase when we can pass it as a parameter? export async function getToVoteOnParent( - parentId: string, - updateStoreWithVoteCB: (statement: Statement) => void, + parentId: string, + updateStoreWithVoteCB: (statement: Statement) => void ): Promise { - try { - const user = getUserFromFirebase(); - if (!user) throw new Error("User not logged in"); - if(!parentId) throw new Error("ParentId not provided"); - const voteId = getVoteId(user.uid, parentId); - if (!voteId) throw new Error("VoteId not found"); + try { + const user = getUserFromFirebase(); + if (!user) throw new Error("User not logged in"); + if (!parentId) throw new Error("ParentId not provided"); + const voteId = getVoteId(user.uid, parentId); + if (!voteId) throw new Error("VoteId not found"); - const parentVoteRef = doc( - DB, - Collections.votes, - voteId - ); + const parentVoteRef = doc(DB, Collections.votes, voteId); - const voteDB = await getDoc(parentVoteRef); + const voteDB = await getDoc(parentVoteRef); - const vote = voteDB.data(); - if (!vote) return; // the user has not voted on this statement - VoteSchema.parse(vote); + const vote = voteDB.data(); + if (!vote) return; + VoteSchema.parse(vote); - //get statemtn to update to store - const statementRef = doc(DB, Collections.statements, vote.statementId); - const statementDB = await getDoc(statementRef); + //get statemtn to update to store + const statementRef = doc(DB, Collections.statements, vote.statementId); + const statementDB = await getDoc(statementRef); - const statement = statementDB.data() as Statement; - if (!statement) throw new Error("Parent not found"); - StatementSchema.parse(statement); + const statement = statementDB.data() as Statement; + if (!statement) throw new Error("Parent not found"); + StatementSchema.parse(statement); - updateStoreWithVoteCB(statement); - } catch (error) { - console.error(error); - } + updateStoreWithVoteCB(statement); + } catch (error) { + console.error(error); + } } export async function getVoters(parentId: string): Promise { - try { - const user = store.getState().user.user; - if (!user) throw new Error("User not logged in"); - const votesRef = collection(DB, Collections.votes); - const q = query(votesRef, where("parentId", "==", parentId)); + try { + const user = store.getState().user.user; + if (!user) throw new Error("User not logged in"); + const votesRef = collection(DB, Collections.votes); + const q = query(votesRef, where("parentId", "==", parentId)); - const votersDB = await getDocs(q); - const voters = votersDB.docs.map((vote) => vote.data()) as Vote[]; + const votersDB = await getDocs(q); + const voters = votersDB.docs.map((vote) => vote.data()) as Vote[]; - return voters; - } catch (error) { - console.error(error); + return voters; + } catch (error) { + console.error(error); - return [] as Vote[]; - } + return [] as Vote[]; + } } diff --git a/src/view/pages/statement/components/settings/components/GetVoters.tsx b/src/view/pages/statement/components/settings/components/GetVoters.tsx index 061be4ed0..3c2a7dac5 100644 --- a/src/view/pages/statement/components/settings/components/GetVoters.tsx +++ b/src/view/pages/statement/components/settings/components/GetVoters.tsx @@ -1,28 +1,40 @@ +import { useLanguage } from "@/controllers/hooks/useLanguages"; import { User, Vote } from "delib-npm"; -import React, { FC } from "react"; +import React, { FC, useEffect } from "react"; import { handleGetVoters } from "../statementSettingsCont"; -import { useLanguage } from "@/controllers/hooks/useLanguages"; import MembersChipsList from "./membership/membersChipsList/MembersChipList"; interface GetVotersProps { - statementId: string; + statementId: string; + joinedMembers: User[]; } -const GetVoters: FC = ({ statementId }) => { +const GetVoters: FC = ({ statementId, joinedMembers }) => { const { t } = useLanguage(); const [voters, setVoters] = React.useState([]); - const [clicked, setClicked] = React.useState(false); + const [nonVoters, setNonVoters] = React.useState([]); + const [clickedVoters, setClickedVoters] = React.useState(false); + const [clickedNonVoters, setClickedNonVoters] = React.useState(false); const getVoters = () => { - if (!clicked) { - handleGetVoters(statementId, setVoters, setClicked); + if (!clickedVoters) { + handleGetVoters(statementId, setVoters, setClickedVoters); } else { - setClicked(false); + setClickedVoters(false); } }; - const members = voters.flatMap((voter) => voter.voter as User); + //filter out users who haven't vote/those with no voter information + useEffect(() => { + if (voters.length > 0) { + const voterIds = new Set(voters.map((voter) => voter.voter?.uid)); + const nonVotersList = joinedMembers.filter( + (member) => !voterIds.has(member.uid) + ); + setNonVoters(nonVotersList); + } + }, [voters, joinedMembers]); return ( <> @@ -35,17 +47,42 @@ const GetVoters: FC = ({ statementId }) => { {t("Get Voters")} - {clicked && ( + {clickedVoters && ( <> - {members.length > 0 && ( + {voters.length > 0 ? ( <> {voters.length} {t("Voted")} - + v.voter as User)} /> + + ) : ( +
{t("No voters found")}
+ )} + + )} + + + + {clickedNonVoters && ( + <> + {nonVoters.length > 0 ? ( + <> + + {nonVoters.length} {t("Did Not Vote")} + + + ) : ( +
{t("No non-voters found")}
)} - {members.length === 0 &&
{t("No voters found")}
} )} diff --git a/src/view/pages/statement/components/settings/components/statementSettingsForm/StatementSettingsForm.tsx b/src/view/pages/statement/components/settings/components/statementSettingsForm/StatementSettingsForm.tsx index c161582a8..a7f740559 100644 --- a/src/view/pages/statement/components/settings/components/statementSettingsForm/StatementSettingsForm.tsx +++ b/src/view/pages/statement/components/settings/components/statementSettingsForm/StatementSettingsForm.tsx @@ -2,7 +2,7 @@ import { Dispatch, FC } from 'react'; // Third party imports import { useNavigate, useParams } from 'react-router-dom'; -import { Statement } from 'delib-npm'; +import { Role, Statement, StatementSubscription } from 'delib-npm'; // Firestore functions @@ -26,6 +26,9 @@ import './StatementSettingsForm.scss'; // icons import SaveIcon from '@/assets/icons/save.svg?react'; import QuestionSettings from '../QuestionSettings/QuestionSettings'; +import { useAppSelector } from '@/controllers/hooks/reduxHooks'; +import { createSelector } from '@reduxjs/toolkit'; +import { RootState } from '@/model/store'; interface StatementSettingsFormProps { setIsLoading: (isLoading: boolean) => void; @@ -46,6 +49,24 @@ const StatementSettingsForm: FC = ({ const { statementId } = useParams(); const { t } = useLanguage(); + // Selector to get the statement memberships + const statementMembershipSelector = (statementId: string | undefined) => + createSelector( + (state: RootState) => state.statements.statementMembership, + (memberships) => + memberships.filter( + (membership: StatementSubscription) => + membership.statementId === statementId + ) + ); + + const members: StatementSubscription[] = useAppSelector( + statementMembershipSelector(statementId) + ); + + const joinedMembers = members.filter((member) => member.role !== Role.banned).map(m => m.user); + + // * Functions * // const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -77,7 +98,6 @@ const StatementSettingsForm: FC = ({
@@ -92,12 +112,12 @@ const StatementSettingsForm: FC = ({ - +
- +
- +
)} diff --git a/src/view/pages/statement/components/settings/statementSettingsCont.ts b/src/view/pages/statement/components/settings/statementSettingsCont.ts index 8bb81c342..c9416e855 100644 --- a/src/view/pages/statement/components/settings/statementSettingsCont.ts +++ b/src/view/pages/statement/components/settings/statementSettingsCont.ts @@ -1,21 +1,21 @@ import { - Statement, - NavObject, - Vote, - Evaluation, - StatementType, - Screen, -} from 'delib-npm'; + Statement, + NavObject, + Vote, + Evaluation, + StatementType, + Screen, +} from "delib-npm"; // Helpers -import { getVoters } from '@/controllers/db/vote/getVotes'; -import { getEvaluations } from '@/controllers/db/evaluation/getEvaluation'; -import { navigateToStatementTab } from '@/controllers/general/helpers'; +import { getVoters } from "@/controllers/db/vote/getVotes"; +import { getEvaluations } from "@/controllers/db/evaluation/getEvaluation"; +import { navigateToStatementTab } from "@/controllers/general/helpers"; import { - createStatement, - setStatementToDB, - updateStatement, -} from '@/controllers/db/statements/setStatements'; + createStatement, + setStatementToDB, + updateStatement, +} from "@/controllers/db/statements/setStatements"; import { defaultResultsSettings, defaultStatementSettings, @@ -25,233 +25,251 @@ import { NavigateFunction } from 'react-router-dom'; // Get users that voted on options in this statement export async function handleGetVoters( - parentId: string | undefined, - setVoters: React.Dispatch>, - setClicked: React.Dispatch> + parentId: string | undefined, + setVoters: React.Dispatch>, + setClicked: React.Dispatch> ) { - if (!parentId) return; - const voters = await getVoters(parentId); - setVoters(voters); - setClicked(true); + if (!parentId) return; + const voters = await getVoters(parentId); + setVoters(voters); + setClicked(true); +} + +//Get users that did not vote on options in this statement +export async function handleGetNonVoters( + parentId: string | undefined, + setNonVoters: React.Dispatch>, + setClicked: React.Dispatch> +) { + if (!parentId) return; + + try { + const voters = await getVoters(parentId); + + // Filter out users who haven't voted (those with no voter information) + const nonVoters = voters.filter((voter) => !voter.voter); + + setNonVoters(nonVoters); + + setClicked(true); + } catch (error) { + console.error("Error fetching non-voters:", error); + } } // Get users that evaluated on options in this statement export async function handleGetEvaluators( - parentId: string | undefined, - setEvaluators: React.Dispatch>, - setClicked: React.Dispatch> + parentId: string | undefined, + setEvaluators: React.Dispatch>, + setClicked: React.Dispatch> ) { - if (!parentId) return; - const evaluators = await getEvaluations(parentId); - setEvaluators(evaluators); - setClicked(true); + if (!parentId) return; + const evaluators = await getEvaluations(parentId); + setEvaluators(evaluators); + setClicked(true); } // Check if sub-page is checked in stored statement export function isSubPageChecked( - statement: Statement | undefined, - navObj: NavObject + statement: Statement | undefined, + navObj: NavObject ): boolean { - try { - //in case of a new statement - if (!statement) { - if (navObj.default === false) return false; - else return true; - } - - //in case of an existing statement - const { subScreens } = statement; - if (!subScreens) return true; - if (subScreens.includes(navObj.link)) return true; - - return false; - } catch (error) { - console.error(error); - - return true; - } + try { + //in case of a new statement + if (!statement) { + if (navObj.default === false) return false; + else return true; + } + + //in case of an existing statement + const { subScreens } = statement; + if (!subScreens) return true; + if (subScreens.includes(navObj.link)) return true; + + return false; + } catch (error) { + console.error(error); + + return true; + } } interface HandleSetStatementParams { - navigate: NavigateFunction; - statementId: string | undefined; - statement: Statement; - parentStatement?: Statement | 'top'; + navigate: NavigateFunction; + statementId: string | undefined; + statement: Statement; + parentStatement?: Statement | "top"; } export async function handleSetStatement({ - navigate, - statementId, - statement, - parentStatement, + navigate, + statementId, + statement, + parentStatement, }: HandleSetStatementParams) { - try { - - - - - // If statement title is empty, don't save - if(!statement.statement) return; - - const { - hasChildren, - resultsBy, - numberOfResults, - enableAddEvaluationOption, - enableAddVotingOption, - enhancedEvaluation, - showEvaluation, - subScreens, - membership, - } = getSetStatementData(statement); - - // If no statementId, user is on AddStatement page - if (!statementId) { - const newStatement = createStatement({ - text: statement.statement, - description: statement.description, - subScreens, - statementType: StatementType.question, - parentStatement: 'top', - resultsBy, - numberOfResults, - hasChildren, - enableAddEvaluationOption, - enableAddVotingOption, - enhancedEvaluation, - showEvaluation, - membership, - }); - if (!newStatement) throw new Error('newStatement had error in creating'); - - await setStatementToDB({ - parentStatement: 'top', - statement: newStatement, - addSubscription: true, - }); - navigateToStatementTab(newStatement, navigate); - - return; - } - - // If statementId, user is on Settings tab in statement page - else { - // update statement - if (!statement) throw new Error('statement is undefined'); - - const newStatement = updateStatement({ - statement, - text: statement.statement, - description: statement.description||'', - subScreens: subScreens, - statementType: StatementType.question, - resultsBy, - numberOfResults, - hasChildren, - enableAddEvaluationOption, - enableAddVotingOption, - enhancedEvaluation, - showEvaluation, - membership, - }); - if (!newStatement) throw new Error('newStatement had not been updated'); - - await setStatementToDB({ - parentStatement, - statement: newStatement, - addSubscription: true, - }); - navigateToStatementTab(newStatement, navigate); - - return; - } - } catch (error) { - console.error(error); - } + try { + // If statement title is empty, don't save + if (!statement.statement) return; + + const { + hasChildren, + resultsBy, + numberOfResults, + enableAddEvaluationOption, + enableAddVotingOption, + enhancedEvaluation, + showEvaluation, + subScreens, + membership, + } = getSetStatementData(statement); + + // If no statementId, user is on AddStatement page + if (!statementId) { + const newStatement = createStatement({ + text: statement.statement, + description: statement.description, + subScreens, + statementType: StatementType.question, + parentStatement: "top", + resultsBy, + numberOfResults, + hasChildren, + enableAddEvaluationOption, + enableAddVotingOption, + enhancedEvaluation, + showEvaluation, + membership, + }); + if (!newStatement) throw new Error("newStatement had error in creating"); + + await setStatementToDB({ + parentStatement: "top", + statement: newStatement, + addSubscription: true, + }); + navigateToStatementTab(newStatement, navigate); + + return; + } + + // If statementId, user is on Settings tab in statement page + else { + // update statement + if (!statement) throw new Error("statement is undefined"); + + const newStatement = updateStatement({ + statement, + text: statement.statement, + description: statement.description || "", + subScreens: subScreens, + statementType: StatementType.question, + resultsBy, + numberOfResults, + hasChildren, + enableAddEvaluationOption, + enableAddVotingOption, + enhancedEvaluation, + showEvaluation, + membership, + }); + if (!newStatement) throw new Error("newStatement had not been updated"); + + await setStatementToDB({ + parentStatement, + statement: newStatement, + addSubscription: true, + }); + navigateToStatementTab(newStatement, navigate); + + return; + } + } catch (error) { + console.error(error); + } } - - - - export const getStatementSettings = (statement: Statement) => { - const statementSettings = - statement.statementSettings ?? defaultStatementSettings; - - return { - enableAddEvaluationOption: Boolean( - statementSettings.enableAddEvaluationOption - ), - enableAddVotingOption: Boolean(statementSettings.enableAddVotingOption), - enhancedEvaluation: Boolean(statementSettings.enhancedEvaluation), - showEvaluation: Boolean(statementSettings.showEvaluation), - subScreens: statementSettings.subScreens ?? [], - inVotingGetOnlyResults: Boolean(statementSettings.inVotingGetOnlyResults), - enableSimilaritiesSearch: Boolean(statementSettings.enableSimilaritiesSearch), - enableNavigationalElements: Boolean(statementSettings.enableNavigationalElements), - }; + const statementSettings = + statement.statementSettings ?? defaultStatementSettings; + + return { + enableAddEvaluationOption: Boolean( + statementSettings.enableAddEvaluationOption + ), + enableAddVotingOption: Boolean(statementSettings.enableAddVotingOption), + enhancedEvaluation: Boolean(statementSettings.enhancedEvaluation), + showEvaluation: Boolean(statementSettings.showEvaluation), + subScreens: statementSettings.subScreens ?? [], + inVotingGetOnlyResults: Boolean(statementSettings.inVotingGetOnlyResults), + enableSimilaritiesSearch: Boolean( + statementSettings.enableSimilaritiesSearch + ), + enableNavigationalElements: Boolean( + statementSettings.enableNavigationalElements + ), + }; }; const getStatementSubScreens = (statement: Statement) => { - const defaultSubScreens = [Screen.CHAT, Screen.OPTIONS]; - const subScreens = statement.subScreens ?? defaultSubScreens; + const defaultSubScreens = [Screen.CHAT, Screen.OPTIONS]; + const subScreens = statement.subScreens ?? defaultSubScreens; - // don't allow setting sub-screens as an empty array - return subScreens.length === 0 ? defaultSubScreens : subScreens; + // don't allow setting sub-screens as an empty array + return subScreens.length === 0 ? defaultSubScreens : subScreens; }; const getSetStatementData = (statement: Statement) => { - const { resultsBy, numberOfResults } = - statement.resultsSettings ?? defaultResultsSettings; - const { - enableAddEvaluationOption, - enableAddVotingOption, - enhancedEvaluation, - showEvaluation, - } = getStatementSettings(statement); - - return { - hasChildren: Boolean(statement.hasChildren), - subScreens: getStatementSubScreens(statement), - resultsBy, - numberOfResults, - enableAddEvaluationOption, - enableAddVotingOption, - enhancedEvaluation, - showEvaluation, - membership: statement.membership, - }; + const { resultsBy, numberOfResults } = + statement.resultsSettings ?? defaultResultsSettings; + const { + enableAddEvaluationOption, + enableAddVotingOption, + enhancedEvaluation, + showEvaluation, + } = getStatementSettings(statement); + + return { + hasChildren: Boolean(statement.hasChildren), + subScreens: getStatementSubScreens(statement), + resultsBy, + numberOfResults, + enableAddEvaluationOption, + enableAddVotingOption, + enhancedEvaluation, + showEvaluation, + membership: statement.membership, + }; }; interface ToggleSubScreenParams { - subScreens: Screen[]; - screenLink: Screen; - statement: Statement; + subScreens: Screen[]; + screenLink: Screen; + statement: Statement; } export const toggleSubScreen = ({ - subScreens, - screenLink, - statement, + subScreens, + screenLink, + statement, }: ToggleSubScreenParams): Statement => { - const checked = subScreens.includes(screenLink) ?? false; - const newSubScreens = checked - ? subScreens.filter((subScreen) => subScreen !== screenLink) - : [...subScreens, screenLink]; - - return { - ...statement, - subScreens: newSubScreens, - }; + const checked = subScreens.includes(screenLink) ?? false; + const newSubScreens = checked + ? subScreens.filter((subScreen) => subScreen !== screenLink) + : [...subScreens, screenLink]; + + return { + ...statement, + subScreens: newSubScreens, + }; }; interface CreateStatementFromModalParams { - title: string; - description: string; - isOptionSelected: boolean; - parentStatement: Statement | 'top'; - toggleAskNotifications?: VoidFunction; - isSendToStoreTemp?: boolean; + title: string; + description: string; + isOptionSelected: boolean; + parentStatement: Statement | "top"; + toggleAskNotifications?: VoidFunction; + isSendToStoreTemp?: boolean; } export async function createStatementFromModal({ @@ -261,24 +279,28 @@ export async function createStatementFromModal({ toggleAskNotifications, parentStatement }: CreateStatementFromModalParams) { - try { - if (!title) throw new Error('title is undefined'); - - - - const newStatement = createStatement({ - ...defaultStatementSettings, - hasChildren: true, - toggleAskNotifications, - text:title, - description, - parentStatement, - statementType: isOptionSelected - ? StatementType.option - : StatementType.question, - }); - - if (!newStatement) throw new Error('newStatement was not created'); + try { + if (!title) throw new Error("title is undefined"); + + const newStatement = createStatement({ + ...defaultStatementSettings, + hasChildren: true, + toggleAskNotifications, + text: title, + description, + parentStatement, + statementType: isOptionSelected + ? StatementType.option + : StatementType.question, + }); + + if (!newStatement) throw new Error("newStatement was not created"); + + await setStatementToDB({ + statement: newStatement, + parentStatement: parentStatement === "top" ? undefined : parentStatement, + addSubscription: true, + }); await setStatementToDB({ statement: newStatement,