diff --git a/.gitignore b/.gitignore index dd3f26e42..6520fbf9c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -congifKey.ts + # Logs @@ -43,3 +43,5 @@ firebase-debug.log firebase-debug.log* ./firestore.indexes.json firestore.rules + +src/controllers/db/configKey.ts diff --git a/firestore.rules b/firestore.rules index 6e311281e..e28a558d0 100644 --- a/firestore.rules +++ b/firestore.rules @@ -81,7 +81,19 @@ service cloud.firestore { allow read: if request.auth.uid != null; allow write: if request.auth.uid != null; } - + match /rooms/{roomId=**}{ + allow read: if request.auth.uid != null; + allow write: if request.auth.uid != null; + } + match /participants/{participantId=**}{ + allow read: if request.auth.uid != null; + allow write: if request.auth.uid != null; + } + match /roomsSettings/{roomSettingsId=**}{ + allow read: if request.auth.uid != null; + allow write: if request.auth.uid != null; + } + match /timers-rooms/{timerIdx=**}{ allow read: if request.auth.uid != null; allow write: if request.auth.uid != null; diff --git a/package-lock.json b/package-lock.json index a9702996a..df19d7431 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@dagrejs/dagre": "^1.0.4", "@reduxjs/toolkit": "^1.9.5", - "delib-npm": "^1.3.35", + "delib-npm": "^1.3.44", "eslint-plugin-import": "^2.29.1", "eslint-plugin-sonarjs": "^1.0.3", "firebase": "^10.0.0", @@ -5486,9 +5486,9 @@ } }, "node_modules/delib-npm": { - "version": "1.3.35", - "resolved": "https://registry.npmjs.org/delib-npm/-/delib-npm-1.3.35.tgz", - "integrity": "sha512-+RepbNSzopfC1R49d1V9L1J4EzsDFgy2SYTPrm1DmFQmJJVEyWyZJIypZrfUJ92i3jUf+zATlI4EWmYdi+tcEA==", + "version": "1.3.44", + "resolved": "https://registry.npmjs.org/delib-npm/-/delib-npm-1.3.44.tgz", + "integrity": "sha512-6/5LMhuJ4lekGRVc4sChA6SRgEOxaXUySdswrkENlBMi1htrL8+6aM9zlX/ExJgjfzpOo8cgpibKKyKpENipOQ==", "dependencies": { "react": "^18.3.1", "sass": "^1.77.6", diff --git a/package.json b/package.json index 3995971ee..476edb640 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "dependencies": { "@dagrejs/dagre": "^1.0.4", "@reduxjs/toolkit": "^1.9.5", - "delib-npm": "^1.3.35", + "delib-npm": "^1.3.44", "eslint-plugin-import": "^2.29.1", "eslint-plugin-sonarjs": "^1.0.3", "firebase": "^10.0.0", diff --git a/src/assets/images/roomImage.png b/src/assets/images/roomImage.png new file mode 100644 index 000000000..f917746d3 Binary files /dev/null and b/src/assets/images/roomImage.png differ diff --git a/src/controllers/db/configKey.ts b/src/controllers/db/configKey.ts deleted file mode 100644 index 290d71d86..000000000 --- a/src/controllers/db/configKey.ts +++ /dev/null @@ -1,35 +0,0 @@ -const mode = import.meta.env.VITE_APP_ENV as "development" | "production"; - - -const firebaseConfig = { - development: { - apiKey: import.meta.env.VITE_FIREBASE_API_KEY_DEV, - authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN_DEV, - databaseURL: import.meta.env.VITE_FIREBASE_DATABASE_URL_DEV, - projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID_DEV, - storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET_DEV, - messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID_DEV, - appId: import.meta.env.VITE_FIREBASE_APP_ID_DEV, - measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID_DEV, - }, - production: { - apiKey: import.meta.env.VITE_FIREBASE_API_KEY_PROD, - authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN_PROD, - databaseURL: import.meta.env.VITE_FIREBASE_DATABASE_URL_PROD, - projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID_PROD, - storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET_PROD, - messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID_PROD, - appId: import.meta.env.VITE_FIREBASE_APP_ID_PROD, - measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID_PROD, - }, -}; - - -const vapidKeys = { - development: import.meta.env.VITE_FIREBASE_VAPID_KEY_DEV, - production: import.meta.env.VITE_FIREBASE_VAPID_KEY_PROD, -}; - -export const vapidKey = vapidKeys[mode]; - -export default firebaseConfig[mode]; diff --git a/src/controllers/db/rooms/getRooms.ts b/src/controllers/db/rooms/getRooms.ts index 246ee6e90..eac0a8762 100644 --- a/src/controllers/db/rooms/getRooms.ts +++ b/src/controllers/db/rooms/getRooms.ts @@ -1,92 +1,80 @@ import { Collections, - Participant, - Statement, - StatementSchema, - StatementType, + ParticipantInRoom, + RoomSettings, + Statement } from 'delib-npm'; import { - and, collection, + doc, onSnapshot, - or, query, where, } from 'firebase/firestore'; import { DB } from '../config'; -import { setRoomRequests } from '@/model/rooms/roomsSlice'; +import { deleteRoom, setRoom, setRooms, setRoomSettings } from '@/model/rooms/roomsSlice'; import { Unsubscribe } from 'firebase/auth'; -import { AppDispatch } from '@/model/store'; +import { store } from '@/model/store'; -export function listenToAllRoomsRequest( + +export function listenToParticipants( statement: Statement, - dispatch: AppDispatch ): Unsubscribe { try { - const requestRef = collection(DB, Collections.statementRoomsAsked); - const q = query(requestRef, where('parentId', '==', statement.statementId)); + const dispatch = store.dispatch; + const requestRef = collection(DB, Collections.participants); + const q = query(requestRef, where('statement.parentId', '==', statement.statementId)); - return onSnapshot(q, (requestsDB) => { + return onSnapshot(q, (roomsDB) => { try { - const requests = requestsDB.docs.map( - (requestDB) => requestDB.data() as Participant - ); + roomsDB.docChanges().forEach((change) => { + + const room = change.doc.data() as ParticipantInRoom; + + switch (change.type) { + case 'added': + dispatch(setRoom(room)); + break; + case 'modified': + dispatch(setRoom(room)); + break; + case 'removed': + dispatch(deleteRoom(room)); + break; + default: + break; + } + }); - dispatch(setRoomRequests(requests)); } catch (error) { console.error(error); - dispatch(setRoomRequests([])); + dispatch(setRooms([])); } }); } catch (error) { console.error(error); // eslint-disable-next-line @typescript-eslint/no-empty-function - return () => {}; + return () => { }; } } // TODO: this function is not used. Delete it? -export function listenToRoomSolutions( - statementId: string, - cb: (rooms: Statement | null) => void -) { - try { - const statementSolutionsRef = collection(DB, Collections.statements); - const q = query( - statementSolutionsRef, - and( - where('parentId', '==', statementId), - or( - where('statementType', '==', StatementType.option), - where('statementType', '==', StatementType.result) - ) - ) - ); - return onSnapshot(q, (roomSolutionsDB) => { - try { - roomSolutionsDB.forEach((roomSolutionDB) => { - try { - const roomSolution = roomSolutionDB.data() as Statement; - const { success } = StatementSchema.safeParse(roomSolution); - if (!success) - throw new Error( - 'roomSolution is not valid in listenToRoomSolutions()' - ); +export function listenToRoomsSettings(statementId: string): Unsubscribe { + try { + const roomSettingRef = doc(DB, Collections.roomsSettings, statementId); - cb(roomSolution); - } catch (error) { - console.error(error); - } - }); - } catch (error) { - console.error(error); - cb(null); - } + return onSnapshot(roomSettingRef, (doc) => { + if (!doc.exists()) return; + const roomSettings = doc.data() as RoomSettings; + store.dispatch(setRoomSettings(roomSettings)); }); } catch (error) { console.error(error); + + // eslint-disable-next-line @typescript-eslint/no-empty-function + return () => { }; } } diff --git a/src/controllers/db/rooms/setRooms.ts b/src/controllers/db/rooms/setRooms.ts index 912a8d1ba..5307ab3b0 100644 --- a/src/controllers/db/rooms/setRooms.ts +++ b/src/controllers/db/rooms/setRooms.ts @@ -1,214 +1,196 @@ import { Collections, Statement, - Participant, - getRequestIdToJoinRoom, - RoomsStateSelection, - User, + ParticipantInRoom, + getStatementSubscriptionId, + RoomSettings, } from "delib-npm"; -import { - DocumentData, - DocumentReference, - doc, - getDoc, - setDoc, - updateDoc, -} from "firebase/firestore"; +import { deleteDoc, doc, getDoc, runTransaction, setDoc, updateDoc, writeBatch } from "firebase/firestore"; import { DB } from "../config"; -import { getUserFromFirebase } from "../users/usersGeneral"; -import { ParticipantInRoom } from "@/view/pages/statement/components/rooms/components/adminArrange/AdminArrange"; + + import { store } from "@/model/store"; -export function enterRoomsDB(parentStatement: Statement) { + + +export function setParticipantToDB( + statement: Statement, + roomNumber?: number +): void { try { - const userId = getUserFromFirebase()?.uid; - if (!userId) throw new Error("User not logged in"); - - const requestId = getRequestIdToJoinRoom( - userId, - parentStatement.statementId, - ); - if (!requestId) throw new Error("Request-id is undefined"); - - const statementRef = doc( - DB, - Collections.statementRoomsAsked, - requestId, - ); - const user = getUserFromFirebase(); + const user = store.getState().user.user; if (!user) throw new Error("User not logged in"); - const room: Participant = { - participant: user, - parentId: parentStatement.statementId, - requestId: requestId, - lastUpdate: new Date().getTime(), + + const participantInRoomId = getStatementSubscriptionId(statement.parentId, user); + if (!participantInRoomId) throw new Error("Participant in room id is undefined"); + + const participantInRoom: ParticipantInRoom = { + user: user, + statement, + participantInRoomId }; - setDoc(statementRef, room, { merge: true }); + if (roomNumber) participantInRoom.roomNumber = roomNumber; + + const roomRef = doc(DB, Collections.participants, participantInRoomId); + + setDoc(roomRef, participantInRoom, { merge: true }); } catch (error) { console.error(error); } } -export async function setRoomJoinToDB( - statement: Statement, - participant?: User, - roomNumber?: number, -): Promise { +export function deleteParticipantToDB( + statement: Statement +): void { try { - const { requestDB, user, requestId, requestRef } = - await getRequestFromDB(statement, participant); + const user = store.getState().user.user; + if (!user) throw new Error("User not logged in"); - if (!requestDB.exists()) { - // If there is no request, create one - await saveToDB({ requestId, requestRef, statement, user }); + const roomId = getStatementSubscriptionId(statement.parentId, user); + if (!roomId) throw new Error("Room id is undefined"); - return true; - } else { - //if there is a request - const request = requestDB.data() as Participant; + const roomRef = doc(DB, Collections.participants, roomId); - return await updateRequestToDB(request, requestRef); - } + deleteDoc(roomRef); } catch (error) { console.error(error); + } +} + + +export async function toggleRoomEditingInDB(statementId: string): Promise { + try { + const roomSettingsRef = doc(DB, Collections.roomsSettings, statementId); + + //use transaction + await runTransaction(DB, async (transaction) => { + try { + const roomSettings = (await transaction.get(roomSettingsRef)).data() as RoomSettings; + if (!roomSettings) { + transaction.set(roomSettingsRef, { + statementId, + isEdit: false, + timers: [] + + }); + } else { + + transaction.update(roomSettingsRef, { isEdit: !roomSettings.isEdit }); + } + } catch (error) { + console.error(error); + } + }); - return false; + } catch (error) { + console.error(error); } +} - interface SaveToDB { - requestId: string; - requestRef: DocumentReference; - statement: Statement; - user?: User; - approved?: boolean; - newRoomNumber?: number; - } - - //helpers functions - async function saveToDB({ - requestId, - requestRef, - statement, - user, - approved, - newRoomNumber, - }: SaveToDB) { - const _user = user || store.getState().user.user; - if (!_user) throw new Error("User not logged in"); - const request: Participant = { - statementId: statement.statementId, - participant: _user, - parentId: statement.parentId, - requestId: requestId, - statement, - }; - if (typeof newRoomNumber === "number") - request.roomNumber = newRoomNumber; - if (typeof approved === "boolean") request.approved = approved; - - await setDoc(requestRef, request); - } - async function getRequestFromDB(statement: Statement, participant?: User) { - const user = participant ? participant : store.getState().user.user; - if (!user) throw new Error("User not logged in"); - const userId = user.uid; - if (!userId) throw new Error("User not logged in"); - - const requestId = getRequestIdToJoinRoom(userId, statement.parentId); - if (!requestId) throw new Error("Request id is undefined"); - - const requestRef = doc(DB, Collections.statementRoomsAsked, requestId); - - const requestDB = await getDoc(requestRef); - - return { requestDB, user, requestId, requestRef }; - } - - async function updateRequestToDB( - request: Participant, - requestRef: DocumentReference, - ) { - try { - const user = store.getState().user.user; - if (!user) throw new Error("User not logged in"); - if (request.statement === undefined) { - // If the user is not in a room - await saveToDB({ - requestId: request.requestId, - requestRef, - statement, - user, - approved: request.approved, - }); - - return true; - } else if ( - request.statement.statementId !== statement.statementId - ) { - // If the user is already in a room and wants to join another room - await saveToDB({ - requestId: request.requestId, - requestRef, - statement, - user, - approved: request.approved, - newRoomNumber: roomNumber, - }); - - return true; - } else { - // If the user is already in the same room, remove the user from the room - const { parentId, participant, requestId } = request; - const updatedRequest = { - parentId, - participant, - requestId, - lastUpdate: new Date().getTime(), - }; - await setDoc(requestRef, updatedRequest); - - return false; - } - } catch (error) { - console.error(error); - - return false; - } - } +export async function setNewRoomSettingsToDB(statementId: string): Promise { + try { + const roomSettingsRef = doc(DB, Collections.roomsSettings, statementId); + + const roomSettingsDB = await getDoc(roomSettingsRef); + if (roomSettingsDB.exists()) return; + + const roomSettings: RoomSettings = { + statementId, + isEdit: true, + timers: [], + participantsPerRoom: 7, + }; + setDoc(roomSettingsRef, roomSettings, { merge: true }); + } catch (error) { + console.error(error); + } } -export async function setRoomsStateToDB( - statement: Statement, - roomsState: RoomsStateSelection, -) { +export async function setParticipantsPerRoom({ statementId, add, number }: { statementId: string, add?: number, number?: number }): Promise { try { - //get timers settings from DB - - const statementRef = doc( - DB, - Collections.statements, - statement.statementId, - ); - await setDoc(statementRef, { roomsState }, { merge: true }); + if (!number && !add) throw new Error("number or add must be defined"); + + const roomSettingsRef = doc(DB, Collections.roomsSettings, statementId); + + if (number && number >= 1) { + updateDoc(roomSettingsRef, { participantsPerRoom: number }); + + return; + } + + if (typeof add !== "number") throw new Error("add is not a number"); + + //use transaction + await runTransaction(DB, async (transaction) => { + try { + const roomSettings = (await transaction.get(roomSettingsRef)).data() as RoomSettings; + if (!roomSettings) throw new Error("Room settings not found"); + const participantsPerRoom = roomSettings.participantsPerRoom || 7; + + if (participantsPerRoom + add < 1) return; + + transaction.update(roomSettingsRef, { participantsPerRoom: participantsPerRoom + add }); + + } catch (error) { + console.error(error); + } + }); + } catch (error) { - roomsState; console.error(error); } } -export function setParticipantInRoomToDB(participant: ParticipantInRoom) { +export async function divideParticipantIntoRoomsToDB(topics: Statement[], participants: ParticipantInRoom[], participantsPerRoom: number): Promise { + try { + const _topics = [...topics]; + const _participants = [...participants]; + + const batch = writeBatch(DB); + let roomNumber = 1; + + _topics.forEach((topic) => { + const participantsInTopic = _participants.filter((participant) => participant.statement.statementId === topic.statementId); + participantsInTopic.sort(() => Math.random() - 0.5); + const numberOfRooms = Math.ceil(participantsInTopic.length / participantsPerRoom); + const topRoomNumber = roomNumber + (numberOfRooms - 1); + const initialRoomNumber = roomNumber; + let localRoomNumber = roomNumber; + + + participantsInTopic.forEach((participant) => { + + const participantRef = doc(DB, Collections.participants, participant.participantInRoomId); + + batch.update(participantRef, { roomNumber: localRoomNumber }); + + localRoomNumber++; + + if (localRoomNumber > topRoomNumber) localRoomNumber = initialRoomNumber; + }); + roomNumber = topRoomNumber + 1; + + }); + + await batch.commit(); + + } catch (error) { + console.error(error) + } + +} + +export async function clearRoomsToDB(participants: ParticipantInRoom[]): Promise { try { - const { uid, topic, roomNumber } = participant; - - if (!topic) return; - const requestId = getRequestIdToJoinRoom(uid, topic.parentId); - if (!requestId) throw new Error("Request id is undefined"); + const batch = writeBatch(DB); + participants.forEach((participant) => { + const participantRef = doc(DB, Collections.participants, participant.participantInRoomId); + batch.update(participantRef, { roomNumber: 0 }); + }); + + await batch.commit(); - const requestRef = doc(DB, Collections.statementRoomsAsked, requestId); - if (topic.statementId) - updateDoc(requestRef, { approved: true, roomNumber }); - else updateDoc(requestRef, { approved: false, roomNumber }); } catch (error) { console.error(error); } diff --git a/src/controllers/db/statements/setStatements.ts b/src/controllers/db/statements/setStatements.ts index 2b92e8582..e858748e6 100644 --- a/src/controllers/db/statements/setStatements.ts +++ b/src/controllers/db/statements/setStatements.ts @@ -26,6 +26,7 @@ import { getSiblingOptionsByParentId, } from "@/view/pages/statement/components/vote/statementVoteCont"; import { allowedScreens } from "@/controllers/general/screens"; +import { setNewRoomSettingsToDB } from "../rooms/setRooms"; @@ -64,6 +65,34 @@ export const updateStatementParents = async ( }; const TextSchema = z.string().min(2); + +export function setSubStatementToDB(statement: Statement, title: string, description?: string) { + try { + const newSubStatement = createStatement({ + text: title, + description: description, + parentStatement: statement, + statementType: StatementType.option, + enableAddEvaluationOption: true, + enableAddVotingOption: true, + enhancedEvaluation: true, + showEvaluation: true, + resultsBy: ResultsBy.topOptions, + numberOfResults: 1, + hasChildren: true, + membership: statement.membership, + }); + + if(!newSubStatement) throw new Error("New newSubStatement is undefined"); + + const newSubStatementRef = doc(DB, Collections.statements, newSubStatement.statementId); + setDoc(newSubStatementRef, newSubStatement); + } catch (error) { + console.error(error); + + } +} + interface SetStatementToDBParams { statement: Statement; parentStatement?: Statement | "top"; @@ -160,6 +189,9 @@ export const setStatementToDB = async ({ statementPromises.push(statementPromise); + //add roomSettings + setNewRoomSettingsToDB(statement.statementId); + //add subscription if (addSubscription) { diff --git a/src/controllers/db/timer/setTimer.ts b/src/controllers/db/timer/setTimer.ts index ee0c60f15..082ebe85f 100644 --- a/src/controllers/db/timer/setTimer.ts +++ b/src/controllers/db/timer/setTimer.ts @@ -12,7 +12,6 @@ import { import { DB } from "../config"; import { Collections, - RoomDivied, RoomTimer, RoomTimerSchema, SetTimer, @@ -183,7 +182,7 @@ export async function setTimersInitTimeDB({ interface InitializeTimersDBParams { statementId: string; - rooms: RoomDivied[]; + rooms: Array<{ roomNumber: number }>; } export async function initializeTimersDB({ diff --git a/src/model/rooms/roomsSlice.ts b/src/model/rooms/roomsSlice.ts index fd1031776..5553db4c1 100644 --- a/src/model/rooms/roomsSlice.ts +++ b/src/model/rooms/roomsSlice.ts @@ -1,150 +1,113 @@ import { createSelector, createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { LobbyRooms, Participant, Statement } from "delib-npm"; +import { ParticipantInRoom, RoomSettings } from "delib-npm"; import { updateArray } from "@/controllers/general/helpers"; -import { z } from "zod"; import { RootState } from "../store"; -export interface RoomAdmin { - room: Array; - roomNumber: number; - statement: Statement; -} + interface RoomsState { - rooms: Participant[]; - askToJoinRooms: Participant[]; - lobbyRooms: LobbyRooms[]; + participants: ParticipantInRoom[]; + roomsSettings: RoomSettings[]; } const initialState: RoomsState = { - rooms: [], - askToJoinRooms: [], - lobbyRooms: [], + participants: [], + roomsSettings: [], }; export const roomsSlice = createSlice({ name: "rooms", initialState, reducers: { - setAskToJoinRooms: ( + setRoom: ( state, - action: PayloadAction<{ - request: Participant | undefined; - parentId: string; - }>, + action: PayloadAction, ) => { try { - const { request, parentId } = action.payload; - - if (!request) { - //remove previous room request - - state.askToJoinRooms = state.askToJoinRooms.filter( - (room) => room.parentId !== parentId, - ); - - return; - } - - //set request to join room - state.askToJoinRooms = updateArray( - state.askToJoinRooms, - request, - "requestId", - ); + state.participants = updateArray(state.participants, action.payload, "participantInRoomId"); } catch (error) { console.error(error); } }, - setRoomRequests: (state, action: PayloadAction) => { + setRooms: ( + state, + action: PayloadAction, + ) => { try { - const requests = action.payload; - z.array(z.any()).parse(requests); - - state.askToJoinRooms = requests; + const participants = action.payload; + participants.forEach((room) => { + state.participants = updateArray(state.participants, room, "participantInRoomId"); + }); } catch (error) { console.error(error); } }, - removeFromAskToJoinRooms: (state, action: PayloadAction) => { + deleteRoom: ( + state, + action: PayloadAction, + ) => { try { - const requestId = action.payload; - state.askToJoinRooms = state.askToJoinRooms.filter( - (room) => room.requestId !== requestId, + state.participants = state.participants.filter( + (room) => room.participantInRoomId !== action.payload.participantInRoomId, ); } catch (error) { console.error(error); } }, + setRoomSettings: ( + state, + action: PayloadAction, + ) => { + try { + state.roomsSettings = updateArray(state.roomsSettings, action.payload, "statementId"); + } catch (error) { + console.error(error); + } + } }, }); -export const { setAskToJoinRooms, setRoomRequests, removeFromAskToJoinRooms } = +export const { setRoom, setRooms, deleteRoom, setRoomSettings } = roomsSlice.actions; -export const participantsSelector = +export const participantByIdSelector = (participantId: string | undefined) => createSelector( + (state: RootState) => state.rooms.participants, + (prt) => prt.find( + (prt) => prt.user.uid === participantId, + ) +); + +export const participantsByTopicId = + (topicId: string | undefined) => createSelector( + (state: RootState) => state.rooms.participants, + (prt) => prt.filter( + (prt) => prt.statement.statementId === topicId, + ) + ); +export const participantsByStatementId = (statementId: string | undefined) => createSelector( - (state: RootState) => state.rooms.askToJoinRooms, - (askToJoinRooms) => askToJoinRooms.filter( - (room) => room.parentId === statementId, + (state: RootState) => state.rooms.participants, + (prt) => prt.filter( + (prt) => prt.statement.parentId === statementId, ) ); -export const askToJoinRoomsSelector = (state: RootState) => - state.rooms.askToJoinRooms; -export const askToJoinRoomSelector = - (statementId: string | undefined) => (state: RootState) => - state.rooms.askToJoinRooms.find( - (room) => room.statementId === statementId, - ); -export const userSelectedRoomSelector = - (statementId: string | undefined) => (state: RootState) => - state.rooms.askToJoinRooms.find( - (room) => - room.participant.uid === state.user.user?.uid && - room.parentId === statementId, - ); -export const topicParticipantsSelector = (statementId: string | undefined) => createSelector( - (state: RootState) => state.rooms.askToJoinRooms, - (askToJoinRooms) => - askToJoinRooms.filter((room) => room.statementId === statementId) -); - -// export const topicParticipantsSelector = createSelector( -// (statementId: string | undefined) => statementId, -// (state: RootState) => state.rooms.askToJoinRooms, -// (statementId, askToJoinRooms) => -// askToJoinRooms.filter((room) => room.statementId === statementId) -// ); -export const participantSelector = - (userId: string | undefined) => (state: RootState) => - state.rooms.askToJoinRooms.filter( - (room) => room.participant.uid === userId, - ); - -//find the user selected topic -export const userSelectedTopicSelector = - (parentId: string | undefined) => (state: RootState) => - state.rooms.askToJoinRooms.find( - (room) => - room.participant.uid === state.user.user?.uid && - room.parentId === parentId, - ); - -//find all user in room number - +export const participantsByStatementIdAndRoomNumber = + (statementId: string | undefined, roomNumber: number) => createSelector( + (state: RootState) => state.rooms.participants, + (prt) => prt.filter( + (prt) => prt.statement.parentId === statementId && prt.roomNumber === roomNumber, + ) + ); -export const participantsInRoomSelector = - (roomNumber: number | undefined, parentId: string) => createSelector( - (state: RootState) => state.rooms.askToJoinRooms, - (askToJoinRooms) => askToJoinRooms.filter( - (room) => room.roomNumber === roomNumber && room.parentId === parentId, +export const roomSettingsByStatementId = + (statementId: string | undefined) => createSelector( + (state: RootState) => state.rooms.roomsSettings, + (roomsSettings) => roomsSettings.find( + (rs) => rs.statementId === statementId, ) ); -//lobby rooms -export const lobbyRoomsSelector = (state: RootState) => state.rooms.lobbyRooms; -export const lobbyRoomSelector = - (statementId: string | undefined) => (state: RootState) => - state.rooms.lobbyRooms.find((room) => room.statementId === statementId); + + export default roomsSlice.reducer; diff --git a/src/view/components/buttons/button/Button.scss b/src/view/components/buttons/button/Button.module.scss similarity index 87% rename from src/view/components/buttons/button/Button.scss rename to src/view/components/buttons/button/Button.module.scss index 9726eabe4..a56dd768e 100644 --- a/src/view/components/buttons/button/Button.scss +++ b/src/view/components/buttons/button/Button.module.scss @@ -3,6 +3,10 @@ display: flex; width: fit-content; align-items: center; + background-color: var(--btn-primary); + padding: .5rem 1.5rem; + border-radius: var(--radius); + color:var(--white); &__text { font-size: 1rem; diff --git a/src/view/components/buttons/button/Button.tsx b/src/view/components/buttons/button/Button.tsx index 4d6a81133..0687d655b 100644 --- a/src/view/components/buttons/button/Button.tsx +++ b/src/view/components/buttons/button/Button.tsx @@ -1,5 +1,5 @@ import { FC } from "react"; -import "./Button.scss"; +import styles from "./Button.module.scss"; import { useLanguage } from "@/controllers/hooks/useLanguages"; interface Props { @@ -28,13 +28,13 @@ const Button: FC = ({ } } - const buttonClassName = `button ${iconOnRight ? "" : "button--right"} ${Icon ? "button--with-icon" : ""} ${className}`.trim(); + // const buttonClassName = `button ${iconOnRight ? "" : "button--right"} ${Icon ? "button--with-icon" : ""} ${className}`.trim(); return ( - - ) : ( - - )} - - {roomsState === RoomsStateSelection.chooseRoom ? ( -
-

{t("Participants")}

-

- { - (t( - "Maximum number of participants in the room ", - ), - maxParticipantsPerRoom) - } -

- -
- -
-
-
-
- {participants.map((request) => ( - - ))} -
-
- ) : ( - <> -

{t("Division into rooms")}

-
- {roomsAdmin.map((room: RoomDivied) => { - return ( - - ); - })} -
- - )} - - - ); -}; - -export default AdminSeeAllGroups; diff --git a/src/view/pages/statement/components/rooms/components/adminArrange/AdminArrangeCont.ts b/src/view/pages/statement/components/rooms/components/adminArrange/AdminArrangeCont.ts deleted file mode 100644 index 74acc624b..000000000 --- a/src/view/pages/statement/components/rooms/components/adminArrange/AdminArrangeCont.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Participant, RoomDivied, Statement } from "delib-npm"; - -interface Topic { - [key: string]: { - statementId: string; - statement: Statement | string | undefined; - participants: Participant[]; - rooms?: Array>; - }; -} - -export function divideIntoTopics( - participants: Participant[], - maxPerRoom = 7, -): { rooms: RoomDivied[]; topicsParticipants: Topic } { - try { - - const topicsParticipants: Topic = {}; - - //build topicsParticipantsObject - participants.forEach((participant) => { - try { - if (!participant.statementId) { - topicsParticipants["general"] = { - statementId: "general", - statement: "General", - participants: [participant], - }; - } else if (!(participant.statementId in topicsParticipants)) { - topicsParticipants[participant.statementId] = { - statementId: participant.statementId, - statement: participant.statement, - participants: [participant], - }; - } else { - topicsParticipants[ - participant.statementId - ].participants.push(participant); - } - } catch (error) { - console.error(error); - - return undefined; - } - }); - - //divide participents according to topics and rooms - // let rooms: Array = []; - for (const topic in topicsParticipants) { - const patricipantsInTopic = topicsParticipants[topic].participants; - topicsParticipants[topic].rooms = - divideParticipantsIntoRoomsRandomly( - patricipantsInTopic, - maxPerRoom, - ); - } - - const rooms = divideIntoGeneralRooms(topicsParticipants); - - return { rooms, topicsParticipants }; - } catch (error) { - console.error(error); - - return { rooms: [], topicsParticipants: {} }; - } -} - -function divideParticipantsIntoRoomsRandomly( - participants: Participant[], - maxPerRoom: number, -): Array> { - try { - const numberOfRooms = Math.ceil(participants.length / maxPerRoom); - - //randomize participants - participants.sort(() => Math.random() - 0.5); - - let roomNumber = 0; - - const rooms: Array> = [[]]; - participants.forEach((participant: Participant) => { - if (!rooms[roomNumber]) rooms[roomNumber] = []; - rooms[roomNumber].push(participant); - if (roomNumber < numberOfRooms - 1) roomNumber++; - else roomNumber = 0; - }); - - return rooms; - } catch (error) { - console.error(error); - - return []; - } -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function divideIntoGeneralRooms(topics: any): Array { - try { - let roomNumber = 1; - const rooms: Array = []; - for (const topic in topics) { - const topicRooms = topics[topic].rooms; - topicRooms.forEach((participants: Array) => { - rooms.push({ - participants, - roomNumber, - statement: topics[topic].statement, - }); - roomNumber++; - }); - } - - return rooms; - } catch (error) { - console.error(error); - - return []; - } -} diff --git a/src/view/pages/statement/components/rooms/components/choose/ChooseRoom.module.scss b/src/view/pages/statement/components/rooms/components/choose/ChooseRoom.module.scss index f4b42aa3b..fadd4100d 100644 --- a/src/view/pages/statement/components/rooms/components/choose/ChooseRoom.module.scss +++ b/src/view/pages/statement/components/rooms/components/choose/ChooseRoom.module.scss @@ -1,19 +1,4 @@ -.fav{ - background-color: var(--blue); - color:white; - height:13rem; - width:3rem; - border-radius: 6rem; - display: flex; - align-items: center; - justify-content: center; - position:relative; - right:50%; - transform: translateX(50%); - cursor:pointer; - transition: all 0.3s; - box-shadow: 4px 4px 10px rgba(0,0,0,0.2); - &:hover{ - background-color: var(--blue-dark); - } +.wrapper{ + padding: 1rem; + margin: 1rem 0; } \ No newline at end of file diff --git a/src/view/pages/statement/components/rooms/components/choose/ChooseRoom.tsx b/src/view/pages/statement/components/rooms/components/choose/ChooseRoom.tsx index cfd957ef0..07e5a401b 100644 --- a/src/view/pages/statement/components/rooms/components/choose/ChooseRoom.tsx +++ b/src/view/pages/statement/components/rooms/components/choose/ChooseRoom.tsx @@ -1,4 +1,4 @@ -import { FC } from "react"; +import { FC, useState } from "react"; // Third party import { Statement } from "delib-npm"; @@ -6,40 +6,47 @@ import { Statement } from "delib-npm"; // Custom components import RoomChoosingCard from "./roomChoosingCard/RoomChoosingCard"; import { useLanguage } from "@/controllers/hooks/useLanguages"; - import styles from "./ChooseRoom.module.scss"; +import Button from "@/view/components/buttons/button/Button"; + +import CreateStatementModal from "../../../createStatementModal/CreateStatementModal"; + + interface Props { - subStatements: Statement[]; - setShowModal: React.Dispatch>; + statement: Statement; + topics: Statement[]; } -const ChooseRoom: FC = ({ subStatements, setShowModal }) => { +const ChooseRoom: FC = ({ statement, topics }) => { const { t } = useLanguage(); + const [showModal, setShowModal] = useState(false); return ( - <> -

{t("Division into rooms")}

+
+ +
+
- {subStatements.map((subStatement: Statement) => { - return ( - - ); + {topics.map((topic: Statement) => { + return ; })} -
setShowModal(true)} - > - + -
+
- - + {showModal && ( + + )} +
); + + function handleAddRoom() { + setShowModal(true); + } }; export default ChooseRoom; diff --git a/src/view/pages/statement/components/rooms/components/choose/roomChoosingCard/RoomChoosingCard.module.scss b/src/view/pages/statement/components/rooms/components/choose/roomChoosingCard/RoomChoosingCard.module.scss index 7fa95e158..4cb0cb438 100644 --- a/src/view/pages/statement/components/rooms/components/choose/roomChoosingCard/RoomChoosingCard.module.scss +++ b/src/view/pages/statement/components/rooms/components/choose/roomChoosingCard/RoomChoosingCard.module.scss @@ -14,7 +14,18 @@ transform: scale(0.9); border: 1px solid rgba(0, 0, 0, 0.2); - &__fill { + .title{ + font-size: 1.5rem; + font-weight: 600; + margin-bottom: 0.5rem; + } + .amount{ + font-size: 1.2rem; + font-weight: 500; + margin-bottom: 0.5rem; + } + + .fill { transition: all 100ms ease-in-out; background-color: rgba(2, 197, 2, 0.294); position: absolute; diff --git a/src/view/pages/statement/components/rooms/components/choose/roomChoosingCard/RoomChoosingCard.tsx b/src/view/pages/statement/components/rooms/components/choose/roomChoosingCard/RoomChoosingCard.tsx index 7fc567ff2..5b0cca639 100644 --- a/src/view/pages/statement/components/rooms/components/choose/roomChoosingCard/RoomChoosingCard.tsx +++ b/src/view/pages/statement/components/rooms/components/choose/roomChoosingCard/RoomChoosingCard.tsx @@ -1,89 +1,43 @@ -import { Participant, Statement } from "delib-npm"; +import { Statement } from "delib-npm"; import { FC } from "react"; -import Text from "@/view/components/text/Text"; -import { setRoomJoinToDB } from "@/controllers/db/rooms/setRooms"; -import { useAppSelector } from "@/controllers/hooks/reduxHooks"; -import { statementSelector } from "@/model/statements/statementsSlice"; -import { - topicParticipantsSelector, - userSelectedTopicSelector, -} from "@/model/rooms/roomsSlice"; import styles from "./RoomChoosingCard.module.scss"; +import { useSelector } from "react-redux"; +import { participantsByTopicId, roomSettingsByStatementId } from "@/model/rooms/roomsSlice"; +import { userSelector } from "@/model/users/userSlice"; +import { + deleteParticipantToDB, + setParticipantToDB, +} from "@/controllers/db/rooms/setRooms"; interface Props { - statement: Statement; + topic: Statement; } - -const RoomChoosingCard: FC = ({ statement }) => { - const request = useAppSelector( - userSelectedTopicSelector(statement.parentId), - ); - const roomSize = - useAppSelector(statementSelector(statement.parentId))?.roomSize || 5; - const requestStatementId = request?.statement?.statementId; - - const topicJoiners = useAppSelector( - topicParticipantsSelector(statement.statementId), - ) as Participant[]; - - function handleAskToJoinRoom() { - setRoomJoinToDB(statement); +const RoomChoosingCard: FC = ({ topic }) => { + const user = useSelector(userSelector); + const participants = useSelector(participantsByTopicId(topic.statementId)); + const participant = participants.find((prt) => prt.user.uid === user?.uid); + const roomSettings = useSelector(roomSettingsByStatementId(topic.parentId)); + const participantsPerRoom = roomSettings?.participantsPerRoom || 7; + + function handleJoinRoom() { + if (participant) { + + deleteParticipantToDB(topic); + } else { + + setParticipantToDB(topic); + } } - - const fill = fillHeight(topicJoiners, roomSize); - const borderRadius = fill > 0.9 ? `1rem` : "0px 0px 1rem 1rem"; + let fill = (participants.length / participantsPerRoom) * 100; + if(fill > 100) fill = 100; return ( -
-
- -
-
- {topicJoiners ? topicJoiners.length : 0}/ - {roomSize || 7} -
-
+
+
{topic.statement}
+
{participants.length}/{participantsPerRoom}
+
90?"1rem":"0px 0px 1rem 1rem"}}>
); }; export default RoomChoosingCard; - -function fillHeight(topicJoiners: Participant[], maxRoomJoiners = 5) { - try { - if (!topicJoiners) return 0; - - const joinersCount = topicJoiners.length; - const fill = joinersCount / maxRoomJoiners; - if (fill > 1) return 1; - - return fill; - } catch (error) { - console.error(error); - - return 0; - } -} - -function fillColor(fill: number) { - if (fill < 0.25) return "#c502024b"; - if (fill < 0.5) return "#c595024b"; - if (fill < 0.75) return "#c4c5024b"; - if (fill >= 1) return "rgba(2, 197, 2, 0.294)"; - - return "gray"; -} diff --git a/src/view/pages/statement/components/rooms/components/inRoom/InRoom.module.scss b/src/view/pages/statement/components/rooms/components/inRoom/InRoom.module.scss new file mode 100644 index 000000000..3a9754b70 --- /dev/null +++ b/src/view/pages/statement/components/rooms/components/inRoom/InRoom.module.scss @@ -0,0 +1,51 @@ +.inRoom { + + margin-top: 2rem; + max-width: 75ch; + margin: 0 auto; + width: 100%; + + padding: 0 1rem; + .wrapper{ + display: flex; + flex-direction: column; + justify-content: space-between; + gap: 1rem; + margin-bottom: 2rem; + margin-top: 2rem; + } + + .room{ + color:var(--text-headline); + font-weight:var(--wight-medium); + font-size: 1.1rem; + line-height: 2rem; + } + .topic{ + color:var(--text-paragraph); + font-weight:var(--wight-medium); + font-size: 1.1rem; + margin-bottom: 3rem; + + } + + .image { + + width: 100%; + height: 300px; + max-height: 30vh; + background-repeat: no-repeat; + background-size: contain; + background-position-x: center; + margin-bottom: 2rem; + } + + .participants{ + display: flex; + justify-content: start; + gap: .2rem; + margin-bottom: 2rem; + flex-wrap: wrap; + } +} + diff --git a/src/view/pages/statement/components/rooms/components/inRoom/InRoom.scss b/src/view/pages/statement/components/rooms/components/inRoom/InRoom.scss deleted file mode 100644 index 98ca475e8..000000000 --- a/src/view/pages/statement/components/rooms/components/inRoom/InRoom.scss +++ /dev/null @@ -1,54 +0,0 @@ -.in-room { - - margin-top: 2rem; - max-width: 75ch; - margin: 0 auto; - width: 100%; - min-height: 100vh; - padding: 0 1rem; - - .welcome { - margin-bottom: 3rem; - - h2 { - color: var(--h2Color); - - text-align: inherit; - } - - - - h3 { - color: var(--subTopic); - text-align: inherit; - margin-top: 1rem; - } - } - .digits{ - position:absolute; - font-size: 2rem; - transform: translateY(-50%); - } - - .participants { - margin: 1rem 0; - display: flex; - gap: 0.5rem; - align-items: center; - - .name { - font-size: .85rem; - } - } - - .image { - background-image: url('../../../../../../../assets/images/teaching.png'); - width: 100%; - min-height: 300px; - background-repeat: no-repeat; - background-size: contain; - background-position-x: center; - margin-bottom: 2rem; - } -} - diff --git a/src/view/pages/statement/components/rooms/components/inRoom/InRoom.tsx b/src/view/pages/statement/components/rooms/components/inRoom/InRoom.tsx index 65147341c..bf4be4150 100644 --- a/src/view/pages/statement/components/rooms/components/inRoom/InRoom.tsx +++ b/src/view/pages/statement/components/rooms/components/inRoom/InRoom.tsx @@ -1,89 +1,68 @@ -import { FC, useEffect } from "react"; - -// // Third Party Libraries -import { Participant, RoomTimer, Statement } from "delib-npm"; +import { FC } from "react"; +import { Statement } from "delib-npm"; +import { + participantByIdSelector, + participantsByStatementIdAndRoomNumber, +} from "@/model/rooms/roomsSlice"; // Redux -import { - useAppDispatch, - useAppSelector, -} from "@/controllers/hooks/reduxHooks"; -import { participantsInRoomSelector, userSelectedTopicSelector } from "@/model/rooms/roomsSlice"; +import { useSelector } from "react-redux"; +import { userSelector } from "@/model/users/userSlice"; // Styles -import "./InRoom.scss"; -import participantsIcon from '@/assets/icons/participants.svg' +import styles from "./InRoom.module.scss"; +import RoomImage from "@/assets/images/roomImage.png"; +import { useLanguage } from "@/controllers/hooks/useLanguages"; +import RoomParticipantBadge from "../roomParticipantBadge/RoomParticipantBadge"; // Custom Components -import RoomTimers from "../roomTimer/Timers"; -import { listenToRoomTimers } from "@/controllers/db/timer/getTimer"; -import { Unsubscribe } from "firebase/firestore"; -import { selectRoomTimers } from "@/model/timers/timersSlice"; -import { useLanguage } from "@/controllers/hooks/useLanguages"; -import { getFirstName, getTitle } from "@/controllers/general/helpers"; interface Props { - statement: Statement; + topic: Statement; } -const InRoom: FC = ({ statement }) => { - const { t } = useLanguage(); - - const userTopic: Participant | undefined = useAppSelector( - userSelectedTopicSelector(statement.statementId) - ); - const participants = useAppSelector(participantsInRoomSelector(userTopic?.roomNumber, statement.statementId)) as Participant[]; - - - const timers: RoomTimer[] = useAppSelector(selectRoomTimers); - - const dispatch = useAppDispatch(); - - useEffect(() => { - // eslint-disable-next-line @typescript-eslint/no-empty-function - let unsubscribe: Unsubscribe = () => {}; - if (userTopic?.roomNumber) { - unsubscribe = listenToRoomTimers( - statement.statementId, - userTopic?.roomNumber, - dispatch - ); - } +const InRoom: FC = ({ topic }) => { + try { + const user = useSelector(userSelector); + const {t} = useLanguage(); - return () => { - unsubscribe(); - }; - }, [userTopic?.roomNumber]); + if (!user) return null; + const participantInRoom = useSelector(participantByIdSelector(user.uid)); + const roomNumber = participantInRoom?.roomNumber || 0; + const participants = useSelector( + participantsByStatementIdAndRoomNumber(topic.statementId, roomNumber) + ); - try { return ( -
- {userTopic && userTopic.statement ? ( -
-

- {t("Welcome to room")} {userTopic?.roomNumber} -

-

{t("Room's Topic")}: {getTitle(userTopic?.statement)}

-
- participants - {participants.length} : - - {participants.map((participant) => ( - {getFirstName(participant.participant.displayName)} - ))} - -
+
+
+
{t("Welcome to room")} {roomNumber}
+
+ {t("Topic")}: {participantInRoom?.statement.statement} +
+
+
{t("Participants")}:
+
+ + {participants.map((participant) => { + return ( + + ); + })}
- ) : ( -

{t("No Topic Chosen by You")}

- )} -
- +
); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { - return
error: {error.message}
; + } catch (error) { + console.error(error); + + return null; } }; diff --git a/src/view/pages/statement/components/rooms/components/room/Room.module.scss b/src/view/pages/statement/components/rooms/components/room/Room.module.scss new file mode 100644 index 000000000..8190d5e32 --- /dev/null +++ b/src/view/pages/statement/components/rooms/components/room/Room.module.scss @@ -0,0 +1,22 @@ +.room { + border: 1px solid black; + padding: 1rem; + border-radius: 0.6rem; + word-break: break-all; + + .topic { + font-size: 1rem; + font-weight:400; + color:var(--text-paragraph); + } + &Number{ + font-size: 1rem; + font-weight: 400; + } + .participants{ + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 1rem; + } +} diff --git a/src/view/pages/statement/components/rooms/components/room/Room.scss b/src/view/pages/statement/components/rooms/components/room/Room.scss deleted file mode 100644 index 31c2ed6ca..000000000 --- a/src/view/pages/statement/components/rooms/components/room/Room.scss +++ /dev/null @@ -1,19 +0,0 @@ -.room { - border: 1px solid black; - padding: 1rem; - border-radius: 0.6rem; - - h4 { - color: gray; - font-size: 1.3rem; - font-weight: bold; - margin: 0px 0rem 0.6rem 0px; - } - - .room-badges { - display: flex; - gap: 0.5rem; - margin-bottom: 0.5rem; - flex-wrap: wrap; - } -} diff --git a/src/view/pages/statement/components/rooms/components/room/Room.tsx b/src/view/pages/statement/components/rooms/components/room/Room.tsx index 047bd460a..203640017 100644 --- a/src/view/pages/statement/components/rooms/components/room/Room.tsx +++ b/src/view/pages/statement/components/rooms/components/room/Room.tsx @@ -1,77 +1,33 @@ import { FC } from "react"; -import Text from "../../../../../../components/text/Text"; -import { Participant, RoomDivied } from "delib-npm"; + +import { ParticipantInRoom, Statement } from "delib-npm"; + +// import { useLanguage } from "@/controllers/hooks/useLanguages"; +import styles from "./Room.module.scss"; import RoomParticipantBadge from "../roomParticipantBadge/RoomParticipantBadge"; -import { setRoomJoinToDB } from "@/controllers/db/rooms/setRooms"; -import { store } from "@/model/store"; -import { useLanguage } from "@/controllers/hooks/useLanguages"; -import "./Room.scss"; interface Props { - room: RoomDivied; - maxParticipantsPerRoom: number; + participants: ParticipantInRoom[]; + roomNumber: number; + topic: Statement; } -const Room: FC = ({ room, maxParticipantsPerRoom }) => { - const { t } = useLanguage(); - - function handleMoveParticipantToRoom(ev: React.DragEvent) { - try { - ev.preventDefault(); - - const draggedParticipantId = ev.dataTransfer.getData("text/plain"); - - const participant = store - .getState() - .rooms.askToJoinRooms.find( - (participant: Participant) => - participant.participant.uid === draggedParticipantId, - ); - - if (!participant) throw new Error("participant not found"); - - if (participant.roomNumber === room.roomNumber) return; - - if (room.participants.length >= maxParticipantsPerRoom) { - alert("room is full"); - - return; - } - setRoomJoinToDB( - room.statement, - participant.participant, - room.roomNumber, - ); - } catch (error) { - console.error(error); - } - } +const Room: FC = ({ participants, roomNumber, topic }) => { + // const { t } = useLanguage(); return ( -
{ - ev.preventDefault(); - }} - onDragLeave={(ev) => { - ev.preventDefault(); - }} - onDragOver={(ev) => { - ev.preventDefault(); - }} - onDrop={handleMoveParticipantToRoom} - > -

- {(t("Room"), room.roomNumber)} -{" "} - -

-
- {room.participants.map((participant: Participant) => ( - - ))} +
+
room: {roomNumber}
+
topic: {topic.statement}
+
+ {participants.map((participant) => { + return ( + + ); + })}
); diff --git a/src/view/pages/statement/components/rooms/components/roomParticipantBadge/RoomParticipantBadge.scss b/src/view/pages/statement/components/rooms/components/roomParticipantBadge/RoomParticipantBadge.scss index d0cbb3b9d..e2f1d86ad 100644 --- a/src/view/pages/statement/components/rooms/components/roomParticipantBadge/RoomParticipantBadge.scss +++ b/src/view/pages/statement/components/rooms/components/roomParticipantBadge/RoomParticipantBadge.scss @@ -3,15 +3,18 @@ color: #7f7f7f; width: fit-content; - border-radius: 30px; + border-radius: 4rem; display: flex; align-items: center; height: 1.6rem; + padding: 0 0px 0px 4px; + gap:0px; + .badge-text { padding: 5px 10px 5px 5px; - font-size: 1rem; - font-weight: 500; + font-size: .8rem; + font-weight: 300; } .badge-image { @@ -23,5 +26,6 @@ background-size: cover; background-repeat: no-repeat; background-position: center; + transform: translateX(5px); } } diff --git a/src/view/pages/statement/components/rooms/components/roomParticipantBadge/RoomParticipantBadge.tsx b/src/view/pages/statement/components/rooms/components/roomParticipantBadge/RoomParticipantBadge.tsx index bbac1ca41..e745c8151 100644 --- a/src/view/pages/statement/components/rooms/components/roomParticipantBadge/RoomParticipantBadge.tsx +++ b/src/view/pages/statement/components/rooms/components/roomParticipantBadge/RoomParticipantBadge.tsx @@ -1,8 +1,9 @@ -import { User } from "delib-npm"; +import { ParticipantInRoom } from "delib-npm"; import { FC } from "react"; +import "./RoomParticipantBadge.scss"; interface Props { - participant: User; + participant: ParticipantInRoom; } const RoomParticipantBadge: FC = ({ participant }) => { @@ -11,14 +12,14 @@ const RoomParticipantBadge: FC = ({ participant }) => { className="room-participant-badge draggable" draggable={true} onDragStart={(e) => { - e.dataTransfer.setData("text/plain", participant.uid); + e.dataTransfer.setData("text/plain", participant.user.uid); }} > -
{participant.displayName}
- {participant.photoURL ? ( +
{participant.user.displayName}
+ {participant.user.photoURL ? (
) : null}
diff --git a/src/view/pages/statement/components/rooms/components/roomsAdmin/RoomsAdmin.module.scss b/src/view/pages/statement/components/rooms/components/roomsAdmin/RoomsAdmin.module.scss new file mode 100644 index 000000000..e414fcae9 --- /dev/null +++ b/src/view/pages/statement/components/rooms/components/roomsAdmin/RoomsAdmin.module.scss @@ -0,0 +1,23 @@ +.participantsPerRoom{ + display: flex; + justify-content: center; + gap: 1rem; + + input{ + width: 3rem; + text-align: center; + } + + .add{ + background-color: var(--btn-tertiary); + display: flex; + justify-content: center; + align-items: center; + width: 2rem; + border-radius: (var(--radius)); + } + +} +.btns{ + margin: .6rem 0rem; +} diff --git a/src/view/pages/statement/components/rooms/components/roomsAdmin/RoomsAdmin.scss b/src/view/pages/statement/components/rooms/components/roomsAdmin/RoomsAdmin.scss deleted file mode 100644 index b2104c557..000000000 --- a/src/view/pages/statement/components/rooms/components/roomsAdmin/RoomsAdmin.scss +++ /dev/null @@ -1,59 +0,0 @@ -.rooms-admin { - padding-bottom: 100px; - width: 100%; - - .title { - color: gray; - font-weight: 500; - text-align: center; - border-bottom: 1px solid gray; - width: 100%; - padding: 0.5rem; - font-size: 1.5rem; - margin-top: 2rem; - margin-bottom: 2rem; - } - - .wrapper { - display: grid; - grid-template-columns: repeat(3, 1fr); - } - - .nav { - display: flex; - justify-content: center; - gap: 1rem; - position: sticky; - width: 100%; - bottom: 0px; - left: 0px; - max-width: 75ch; - margin: auto; - } - - .item { - background-color: white; - color: gray; - flex: 1; - border-radius: 1rem; - text-align: center; - padding: 10px; - cursor: pointer; - transition: all 0.3s ease-in-out; - box-sizing: border-box; - } - - .item__selected { - font-weight: 700; - background-color: white; - border-radius: 1rem; - border: 2px solid var(--accent); - color: var(--accent); - flex: 1; - text-align: center; - padding: 10px; - cursor: pointer; - transition: all 0.3s ease-in-out; - box-sizing: border-box; - } -} diff --git a/src/view/pages/statement/components/rooms/components/roomsAdmin/RoomsAdmin.tsx b/src/view/pages/statement/components/rooms/components/roomsAdmin/RoomsAdmin.tsx index 2232313f6..b4cc7cd5c 100644 --- a/src/view/pages/statement/components/rooms/components/roomsAdmin/RoomsAdmin.tsx +++ b/src/view/pages/statement/components/rooms/components/roomsAdmin/RoomsAdmin.tsx @@ -1,34 +1,55 @@ import { Statement } from "delib-npm"; -import { FC, useState } from "react"; -import AdminArrange from "../adminArrange/AdminArrange"; -import SetTimers from "../setTimers/SetTimers"; +import { FC } from "react"; + import { useLanguage } from "@/controllers/hooks/useLanguages"; -import "./RoomsAdmin.scss"; + +import { setNewRoomSettingsToDB } from "@/controllers/db/rooms/setRooms"; +import { + participantsByStatementId, + roomSettingsByStatementId, +} from "@/model/rooms/roomsSlice"; +import { useSelector } from "react-redux"; +import { statementSubsSelector } from "@/model/statements/statementsSlice"; +import RoomsDivision from "./roomsDivision/RoomsDivision"; +import RoomsView from "./roomsView/RoomsView"; interface Props { - statement: Statement; + statement: Statement; } const RoomsAdmin: FC = ({ statement }) => { + const participants = useSelector( + participantsByStatementId(statement.statementId) + ); + + const topics = useSelector(statementSubsSelector(statement.statementId)); + const { t } = useLanguage(); - const [setRooms, setSetRooms] = useState( - statement.roomsState === "chooseRoom" || - statement.roomsState === undefined - ? false - : true, + const roomSettings = useSelector( + roomSettingsByStatementId(statement.statementId) ); + if (!roomSettings) { + setNewRoomSettingsToDB(statement.statementId); + } + + const isEditingRoom = + roomSettings?.isEdit !== undefined ? roomSettings?.isEdit : true; + return ( -
-

{t("Management board")}

- - - {!setRooms && } +
+

{t("Management board")}

+ {isEditingRoom ? ( + + ) : ( + + )}
); }; diff --git a/src/view/pages/statement/components/rooms/components/roomsAdmin/roomsDivision/RoomsDivision.tsx b/src/view/pages/statement/components/rooms/components/roomsAdmin/roomsDivision/RoomsDivision.tsx new file mode 100644 index 000000000..08c397bf2 --- /dev/null +++ b/src/view/pages/statement/components/rooms/components/roomsAdmin/roomsDivision/RoomsDivision.tsx @@ -0,0 +1,78 @@ +import { FC } from "react"; +import styles from "../RoomsAdmin.module.scss"; +import { + divideParticipantIntoRoomsToDB, + setParticipantsPerRoom, + toggleRoomEditingInDB, +} from "@/controllers/db/rooms/setRooms"; +import { ParticipantInRoom, RoomSettings, Statement } from "delib-npm"; +import Button from "@/view/components/buttons/button/Button"; +import { useLanguage } from "@/controllers/hooks/useLanguages"; + +interface Props { + statement: Statement; + roomSettings: RoomSettings | undefined; + topics: Statement[]; + participants: ParticipantInRoom[]; +} + +const RoomsDivision: FC = ({ + statement, + roomSettings, + topics, + participants, +}) => { + const { t } = useLanguage(); + + function handleToggleEdit() { + toggleRoomEditingInDB(statement.statementId); + + divideParticipantIntoRoomsToDB( + topics, + participants, + roomSettings?.participantsPerRoom || 7 + ); + } + + function handleSetParticipantsPerRoom(add: number) { + setParticipantsPerRoom({ statementId: statement.statementId, add }); + } + + function handleSetParticipantsPerRoomNumber(number: number) { + setParticipantsPerRoom({ statementId: statement.statementId, number }); + } + + return ( + <> +
+
+
+
handleSetParticipantsPerRoom(1)} + > + + +
+ + handleSetParticipantsPerRoomNumber(e.target.valueAsNumber) + } + /> +
handleSetParticipantsPerRoom(-1)} + > + - +
+
+ + ); +}; + +export default RoomsDivision; diff --git a/src/view/pages/statement/components/rooms/components/roomsAdmin/roomsView/RoomsView.module.scss b/src/view/pages/statement/components/rooms/components/roomsAdmin/roomsView/RoomsView.module.scss new file mode 100644 index 000000000..53679d624 --- /dev/null +++ b/src/view/pages/statement/components/rooms/components/roomsAdmin/roomsView/RoomsView.module.scss @@ -0,0 +1,9 @@ +.rooms{ + display: flex; + flex-wrap: wrap; + justify-content: start; + margin-top: 20px; + margin-bottom: 20px; + gap:1rem; + +} \ No newline at end of file diff --git a/src/view/pages/statement/components/rooms/components/roomsAdmin/roomsView/RoomsView.tsx b/src/view/pages/statement/components/rooms/components/roomsAdmin/roomsView/RoomsView.tsx new file mode 100644 index 000000000..308998f5e --- /dev/null +++ b/src/view/pages/statement/components/rooms/components/roomsAdmin/roomsView/RoomsView.tsx @@ -0,0 +1,59 @@ +import { FC } from "react"; +import styles from "./RoomsView.module.scss"; +import Button from "@/view/components/buttons/button/Button"; +import { ParticipantInRoom, Statement } from "delib-npm"; +import { + clearRoomsToDB, + toggleRoomEditingInDB, +} from "@/controllers/db/rooms/setRooms"; +import Room from "../../room/Room"; + +interface Props { + statement: Statement; + participants: ParticipantInRoom[]; +} + +const RoomsView: FC = ({ statement, participants }) => { + function handleToggleEdit() { + toggleRoomEditingInDB(statement.statementId); + clearRoomsToDB(participants); + } + + return ( +
+
+
+
+ {sortIntoRooms(participants).map((roomParticipants, index) => ( + + ))} +
+
+ ); +}; + +export default RoomsView; + +function sortIntoRooms( + participants: ParticipantInRoom[] +): ParticipantInRoom[][] { + const rooms: ParticipantInRoom[][] = []; + participants.forEach((participant) => { + + if (!participant.roomNumber) return; + + const roomIndex = participant.roomNumber - 1; + if (!rooms[roomIndex]) { + rooms[roomIndex] = []; + } + rooms[roomIndex].push(participant); + }); + + return rooms; +} diff --git a/src/view/pages/statement/components/rooms/roomPartitioningController.ts b/src/view/pages/statement/components/rooms/roomPartitioningController.ts deleted file mode 100644 index 99a2763c9..000000000 --- a/src/view/pages/statement/components/rooms/roomPartitioningController.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Participant } from "delib-npm"; - -export function partitionRooms( - participants: Participant[], - maxParticipants: number, - axisId: string, -): Participant[][] { - - //see documentation for more details (partitionProblem.md) - - // Sort the elements in descending order. - const participantsSorted = getSortedPartipants(participants, axisId) - - // Create X empty groups. - const numberOfRooms = Math.ceil( - participantsSorted.length / maxParticipants, - ); - const rooms: Participant[][] = new Array(numberOfRooms); - participantsSorted.forEach((participant) => { - const roomIndex = getMinimalRoomIndex(rooms, axisId); - rooms[roomIndex].push(participant); - }); - - return rooms; -} - -function getMinimalRoomIndex(rooms: Participant[][], axisId: string): number { - const minimalParticipantsRoom: Participant[] = []; - let minimalParticipantsRoomIndex = 0; - - //find the room with the least amount of participants - for (let i = 0; i < rooms.length; i++) { - if (rooms[i].length < minimalParticipantsRoom.length) { - minimalParticipantsRoomIndex = i; - } - } - - //find the room leat amount of participants paradigm value - const roomsValue: number[] = []; - rooms.forEach((room, index) => { - let sumValue = 0; - room.forEach((participant) => { - const paradigm = participant.paradigmAxes?.find( - (paradigm) => paradigm?.paradigmAxis === axisId, - ); - if (paradigm?.value) { - sumValue += paradigm.value; - } - }); - roomsValue[index] = sumValue; - }); - - //return the room with least value in roomsValue array - const minValue: number = Math.min(...roomsValue); - const minimalValueRoomIndex = roomsValue.indexOf(minValue); - - return minimalParticipantsRoomIndex === minimalValueRoomIndex - ? minimalParticipantsRoomIndex - : minimalValueRoomIndex; -} - -function getSortedPartipants(participants:Participant[], axisId:string):Participant[]{ - return participants.sort((a, b) => { - try { - const paradigmA = a.paradigmAxes?.find( - (paradigm) => paradigm?.paradigmAxis === axisId, - ); - const paradigmB = b.paradigmAxes?.find( - (paradigm) => paradigm?.paradigmAxis === axisId, - ); - if (paradigmA === undefined || paradigmB === undefined) - throw new Error("Paradigm not found"); - if (paradigmA.value === undefined || paradigmB.value === undefined) - throw new Error("Paradigm value not found"); - - return paradigmA.value - paradigmB.value; - } catch (error) { - console.error(error); - - return 0; - } - }); -} diff --git a/src/view/pages/statement/components/solutions/StatementSolutionsPage.tsx b/src/view/pages/statement/components/solutions/StatementSolutionsPage.tsx index a1af2957b..20b57e085 100644 --- a/src/view/pages/statement/components/solutions/StatementSolutionsPage.tsx +++ b/src/view/pages/statement/components/solutions/StatementSolutionsPage.tsx @@ -1,43 +1,42 @@ -import { FC, useEffect, useRef, useState } from "react"; +import { FC, useEffect, useState } from 'react'; // Third party imports import { QuestionStage, QuestionType, - Screen, Statement, StatementType, User, isOptionFn, -} from "delib-npm"; -import { useParams, useNavigate } from "react-router"; +} from 'delib-npm'; +import { useParams, useNavigate } from 'react-router'; // Utils & Helpers -import { sortSubStatements } from "./statementSolutionsCont"; +import {sortSubStatements} from './statementSolutionsCont'; // Custom Components -import StatementEvaluationCard from "./components/StatementSolutionCard"; -import StatementBottomNav from "../nav/bottom/StatementBottomNav"; -import Toast from "@/view/components/toast/Toast"; -import Modal from "@/view/components/modal/Modal"; -import StatementInfo from "../vote/components/info/StatementInfo"; -import Button from "@/view/components/buttons/button/Button"; -import LightBulbIcon from "@/assets/icons/lightBulbIcon.svg?react"; -import X from "@/assets/icons/x.svg?react"; -import { useLanguage } from "@/controllers/hooks/useLanguages"; -import { getStagesInfo } from "../settings/components/QuestionSettings/QuestionStageRadioBtn/QuestionStageRadioBtn"; -import { getTitle } from "@/controllers/general/helpers"; -import CreateStatementModalSwitch from "../createStatementModalSwitch/CreateStatementModalSwitch"; -import { getMultiStageOptions } from "@/controllers/db/multiStageQuestion/getMultiStageStatements"; -import styles from "./StatementSolutionsPage.module.scss"; +import StatementEvaluationCard from './components/StatementSolutionCard'; +import StatementBottomNav from '../nav/bottom/StatementBottomNav'; +import Toast from '@/view/components/toast/Toast'; +import Modal from '@/view/components/modal/Modal'; +import StatementInfo from '../vote/components/info/StatementInfo'; +import Button from '@/view/components/buttons/button/Button'; +import LightBulbIcon from '@/assets/icons/lightBulbIcon.svg?react'; +import X from '@/assets/icons/x.svg?react'; +import { useLanguage } from '@/controllers/hooks/useLanguages'; +import { getStagesInfo } from '../settings/components/QuestionSettings/QuestionStageRadioBtn/QuestionStageRadioBtn'; +import { getTitle } from '@/controllers/general/helpers'; +import CreateStatementModalSwitch from '../createStatementModalSwitch/CreateStatementModalSwitch'; +import { getMultiStageOptions } from '@/controllers/db/multiStageQuestion/getMultiStageStatements'; +import styles from './StatementSolutionsPage.module.scss'; interface StatementEvaluationPageProps { - statement: Statement; - subStatements: Statement[]; - handleShowTalker: (talker: User | null) => void; - showNav?: boolean; - questions?: boolean; - toggleAskNotifications: () => void; + statement: Statement; + subStatements: Statement[]; + handleShowTalker: (talker: User | null) => void; + showNav?: boolean; + questions?: boolean; + toggleAskNotifications: () => void; } const StatementEvaluationPage: FC = ({ @@ -53,22 +52,18 @@ const StatementEvaluationPage: FC = ({ const navigate = useNavigate(); const { t } = useLanguage(); - const prevSortRef = useRef(sort as Screen); - const randomSortedRef = useRef>(new Map()); - const isRandomSort = - sort === Screen.OPTIONS_RANDOM || sort === Screen.QUESTIONS_RANDOM; const isMuliStage = - statement.questionSettings?.questionType === QuestionType.multipleSteps; + statement.questionSettings?.questionType === QuestionType.multipleSteps; const currentStage = statement.questionSettings?.currentStage; const stageInfo = getStagesInfo(currentStage); const useSearchForSimilarStatements = - statement.statementSettings?.enableSimilaritiesSearch || false; + statement.statementSettings?.enableSimilaritiesSearch || false; // Use States const [showModal, setShowModal] = useState(false); const [showToast, setShowToast] = useState(false); - + const [showExplanation, setShowExplanation] = useState( currentStage === QuestionStage.explanation && isMuliStage && !questions ); @@ -77,40 +72,10 @@ const StatementEvaluationPage: FC = ({ ); useEffect(() => { - - const isRandomAgain = prevSortRef.current === sort && isRandomSort?true: false; - - - prevSortRef.current = sort as Screen; - - let { subStatements: _sortedSubStatements, subStMap } = sortSubStatements( + const _sortedSubStatements = sortSubStatements( subStatements, - sort, - isRandomAgain, - randomSortedRef.current - ); - randomSortedRef.current = subStMap; - - _sortedSubStatements = _sortedSubStatements.filter((subStatement) => { - //if questions is true, only show questions - if (questions) { - return subStatement.statementType === StatementType.question; - } - - if (isMuliStage) { - //filter the temp presentation designed for this stage - return subStatement.isPartOfTempPresentation; - } - - //if options is true, only show options - return isOptionFn(subStatement); - }); - - setSortedSubStatements(_sortedSubStatements); - }, [sort]); - - useEffect(() => { - const _sortedSubStatements = subStatements.filter((subStatement) => { + sort + ).filter((subStatement) => { //if questions is true, only show questions if (questions) { return subStatement.statementType === StatementType.question; @@ -126,7 +91,7 @@ const StatementEvaluationPage: FC = ({ }); setSortedSubStatements(_sortedSubStatements); - }, [subStatements, questions]); + }, [sort, subStatements, questions]); useEffect(() => { if (questions) { @@ -136,6 +101,7 @@ const StatementEvaluationPage: FC = ({ useEffect(() => { if (isMuliStage) { + getMultiStageOptions(statement); } }, [currentStage]); @@ -146,8 +112,8 @@ const StatementEvaluationPage: FC = ({ } if ( currentStage === QuestionStage.explanation && - isMuliStage && - !questions + isMuliStage && + !questions ) { setShowExplanation(true); } @@ -161,14 +127,15 @@ const StatementEvaluationPage: FC = ({ let topSum = 30; const tops: number[] = [topSum]; const message = stageInfo ? stageInfo.message : false; + return ( <> -
-
+
+
{isMuliStage && message && ( = ({ if (statementSub.elementHight) { topSum += statementSub.elementHight + 30; tops.push(topSum); + } return ( @@ -194,13 +162,13 @@ const StatementEvaluationPage: FC = ({ ); })}
-
+
= ({ case QuestionStage.explanation: return (