diff --git a/backend/app/access/enums.py b/backend/app/access/enums.py new file mode 100644 index 0000000..883d870 --- /dev/null +++ b/backend/app/access/enums.py @@ -0,0 +1,5 @@ +from enum import StrEnum + +class SessionRoles(StrEnum): + CHAIR = "chair" + DELEGATION = "delegate" \ No newline at end of file diff --git a/backend/app/access/models.py b/backend/app/access/models.py index 2a677c5..a088422 100644 --- a/backend/app/access/models.py +++ b/backend/app/access/models.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from typing import Literal from uuid import UUID - +from . import enums @dataclass(frozen=True) class CommitteeAssignment: @@ -9,5 +9,5 @@ class CommitteeAssignment: user_id: UUID committee_id: int # TODO: remove this to map out to committees/conferences - role: Literal["chair", "delegate"] + role: enums.SessionRoles representation_id: int | None diff --git a/backend/app/access/schemas.py b/backend/app/access/schemas.py new file mode 100644 index 0000000..e586898 --- /dev/null +++ b/backend/app/access/schemas.py @@ -0,0 +1,6 @@ +from pydantic import BaseModel +from . import enums + +class SessionRepresentation(BaseModel): + role: enums.SessionRoles + representation_id: int | None \ No newline at end of file diff --git a/backend/app/access/views.py b/backend/app/access/views.py index 7da3c4b..7983f3f 100644 --- a/backend/app/access/views.py +++ b/backend/app/access/views.py @@ -8,16 +8,18 @@ from app.core.database import get_db_session from .service import AccessDenied, resolve_session_assignment +from .schemas import SessionRepresentation router = APIRouter() -@router.get("/sessions/{session_id}/me") + +@router.get("/sessions/{session_id}/me", response_model=SessionRepresentation) async def get_my_session_access( session_id: int, db_session: Annotated[AsyncSession, Depends(get_db_session)], current_user: Annotated[AuthUser, Depends(get_current_user)], -): +)->SessionRepresentation: """Return the authenticated user's actor context for a session.""" try: assignment = await resolve_session_assignment( @@ -32,4 +34,4 @@ async def get_my_session_access( return { "role": assignment.role, "representation_id": assignment.representation_id, - } + } diff --git a/backend/app/session/engine.py b/backend/app/session/engine.py index bd457af..75eab0f 100644 --- a/backend/app/session/engine.py +++ b/backend/app/session/engine.py @@ -448,7 +448,7 @@ def handle_open_informal_voting( ) state.voting = VotingContext( - target_type="INFORMAL", + target_type=enums.VotingType.INFORMAL, title=event.payload.title, return_state=state.current_state, voting_registry={}, @@ -473,7 +473,7 @@ def handle_close_informal_voting( if ( state.current_state != States.VOTING_EXECUTION - or state.voting.target_type != "INFORMAL" + or state.voting.target_type != enums.VotingType.INFORMAL ): raise InvalidProceduralMove("Can't close voting") @@ -497,7 +497,7 @@ def handle_close_procedural_voting( if ( state.current_state != States.VOTING_EXECUTION - or state.voting.target_type != "PROCEDURAL" + or state.voting.target_type != enums.VotingType.PROCEDURAL ): raise InvalidProceduralMove("Can't close voting") @@ -631,13 +631,13 @@ def handle_resolve_motion( if motion is None: raise InvalidProceduralMove("Motion not found") - if payload.action == "ACCEPT": + if payload.action: state.voting = VotingContext( - target_type="PROCEDURAL", + target_type=enums.VotingType.PROCEDURAL, motion_in_vote=motion, return_state=state.current_state, voting_registry={}, - majority="QUALIFIED", # TODO: change depending on type of motion + majority=enums.MajorityTypes.QUALIFIED, # TODO: change depending on type of motion veto_power=True, ) diff --git a/backend/app/session/enums.py b/backend/app/session/enums.py index 0d94d81..7e0f8d4 100644 --- a/backend/app/session/enums.py +++ b/backend/app/session/enums.py @@ -96,8 +96,22 @@ class RollCallChoice(StrEnum): PRESENT_AND_VOTING = "Present and Voting" ABSENT = "Absent" +class MajorityTypes(StrEnum): + SIMPLE = "Maioria Simples" + QUALIFIED = "Maioria Qualificada" + ABSOLUTE = "Consenso" class SessionRole(StrEnum): CHAIR = "CHAIR" DELEGATE = "DELEGATE" # further roles are put here + +class VotingChoice(StrEnum): + FAVOUR = "Favour" + AGAINST = "Against" + ABSTAIN = "Abstain" + +class VotingType(StrEnum): + INFORMAL = "Informal" + PROCEDURAL = "Procedural" + SUBSTANTIVE = "Substantive" diff --git a/backend/app/session/models.py b/backend/app/session/models.py index 4130e64..22f05b6 100644 --- a/backend/app/session/models.py +++ b/backend/app/session/models.py @@ -57,14 +57,14 @@ class QuestionContext(BaseModel): class VotingContext(BaseModel): - target_type: Literal["PROCEDURAL", "SUBSTANTIVE", "INFORMAL"] + target_type: enums.VotingType motion_in_vote: MotionContext | None = None title: str | None = None return_state: enums.States - voting_registry: dict[int, Literal["FAVOUR", "AGAINST", "ABSTAIN"]] = {} + voting_registry: dict[int, enums.VotingChoice] = {} # additional fields - majority: Literal["SIMPLE", "QUALIFIED", "ABSOLUTE"] + majority: enums.MajorityTypes veto_power: bool diff --git a/backend/app/session/schemas.py b/backend/app/session/schemas.py index 9e7cfe5..4c97e89 100644 --- a/backend/app/session/schemas.py +++ b/backend/app/session/schemas.py @@ -35,16 +35,7 @@ class DelegateQuestionPayload(BaseModel): class DelegateVotingPayload(BaseModel): - # other types of voting must be put in here - type: Literal[ - "FORMAL", - "INFORMAL", - ] # perhaps not needed - motion_id: int | None = ( - None # perhaps not needed, unless we pass the voting context to UI to validate? - ) - title: str | None = None # perhaps not needed - vote: Literal["FAVOUR", "AGAINST", "ABSTAIN"] + vote: enums.VotingChoice # TODO: should be better implemented @@ -108,13 +99,13 @@ class ChairToggleTimerPayload(BaseModel): class ChairOpenInformalVotingPayload(BaseModel): # For informal Votings title: str | None = None - majority: Literal["SIMPLE", "QUALIFIED", "ABSOLUTE"] + majority: enums.MajorityTypes veto_power: bool class ChairResolveMotionPayload(BaseModel): motion_id: int # or motion_id if possible - action: Literal["ACCEPT", "DENY"] + action: bool class ChairForceSpeakerPayload(BaseModel): @@ -148,10 +139,10 @@ class MarkAgendaItemPayload(BaseModel): class DeleteAgendaItemPayload(BaseModel): index: str # Agenda Item Id - +#Removed: Unnecessary # These two normally don't need to have an id -class ChairCloseInformalVotingPayload(BaseModel): - voting_id: int | None = None +# class ChairCloseInformalVotingPayload(BaseModel): +# voting_id: int | None = None class EmptyPayload(BaseModel): ... @@ -214,7 +205,7 @@ class SetPhaseEvent(BaseModel): class CloseInformalVotingEvent(BaseModel): type: Literal[enums.ChairEvents.CLOSE_INFORMAL_VOTING] - payload: ChairCloseInformalVotingPayload + payload: EmptyPayload class CloseProceduralVotingEvent(BaseModel): diff --git a/backend/app/tests/session/test_engine.py b/backend/app/tests/session/test_engine.py index ed4003a..f2b2388 100644 --- a/backend/app/tests/session/test_engine.py +++ b/backend/app/tests/session/test_engine.py @@ -119,7 +119,7 @@ def open_informal_voting_event() -> sch.OpenInformalVotingEvent: def close_informal_voting_event() -> sch.CloseInformalVotingEvent: return sch.CloseInformalVotingEvent( type=enums.ChairEvents.CLOSE_INFORMAL_VOTING, - payload=sch.ChairCloseInformalVotingPayload(), + payload=sch.EmptyPayload(), ) diff --git a/docker-compose.yml b/docker-compose.yml index 51a5fd6..4fbd687 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,10 @@ services: watch: - action: restart path: ./backend/app/session/schemas.py + - action: restart + path: ./backend/app/session/models.py + - action: restart + path: ./backend/app/session/enums.py backend: build: @@ -50,12 +54,6 @@ services: retries: 3 start_period: 50s start_interval: 1s - develop: - watch: - - action: restart - path: ./backend/app/session/schemas.py - - action: restart - path: ./backend/app/session/models.py volumes: packages: diff --git a/frontend/src/components/session/Agenda.tsx b/frontend/src/components/session/Agenda.tsx index 4273251..a5ca987 100644 --- a/frontend/src/components/session/Agenda.tsx +++ b/frontend/src/components/session/Agenda.tsx @@ -1,6 +1,6 @@ -import { sendMessage } from "@/pages/Session" +import { sendMessage, useSession } from "@/context/SessionContext" import type {SetAgendaItemEvent, MarkAgendaItemEvent, DeleteAgendaItemEvent} from "@/schemas/types.gen" -import {ChairEvents } from "@/schemas/types.gen" +import {ChairEvents, SessionRoles } from "@/schemas/types.gen" import { Button } from "@/components/ui/button" import { Separator } from "@/components/ui/separator" import { ScrollArea } from "@/components/ui/scroll-area" @@ -31,8 +31,8 @@ import { useRef } from "react" export default function Agenda() { - const isChair = true // Replace with actual logic to determine if the user is the chair - + const {role} = useSession() + const isChair = role===SessionRoles.CHAIR const agendaTopics = useCommitteeStore((state) => state.agenda_topics) const numinput = useRef(null) diff --git a/frontend/src/components/session/bottom-bar-buttons/MotionsButton.tsx b/frontend/src/components/session/bottom-bar-buttons/MotionsButton.tsx index a072e75..b1661ce 100644 --- a/frontend/src/components/session/bottom-bar-buttons/MotionsButton.tsx +++ b/frontend/src/components/session/bottom-bar-buttons/MotionsButton.tsx @@ -40,8 +40,8 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" import { Label } from "@/components/ui/label" import { useCommitteeStore } from "@/store/useCommitteeStore" import { States } from "@/schemas/types.gen" - -const isChair = true // Replace with actual logic to determine if the user is the chair +import { useSession } from "@/context/SessionContext" +import { SessionRoles } from "@/schemas/types.gen" const motions = [ "Moção para Adiamento de Sessão", @@ -89,6 +89,10 @@ function QuestionsMotionsList(type: MotionKind) { } export default function TestButton() { + + const {role} = useSession() + const isChair = role===SessionRoles.CHAIR + const currentState = useCommitteeStore((state) => state.current_state) const [motionKind, setMotionKind] = useState("moção") const [selectedMotion, setSelectedMotion] = useState("") diff --git a/frontend/src/components/session/bottom-bar-buttons/SessionButton.tsx b/frontend/src/components/session/bottom-bar-buttons/SessionButton.tsx index 9cb4098..448661c 100644 --- a/frontend/src/components/session/bottom-bar-buttons/SessionButton.tsx +++ b/frontend/src/components/session/bottom-bar-buttons/SessionButton.tsx @@ -20,7 +20,7 @@ import { } from "@/components/ui/select" import ManualQuorum from "@/components/session/manual-quorum" import { RollCallChoice, ChairEvents, States, type CloseRollCallEvent, type CloseSessionEvent, type OpenSessionEvent } from "@/schemas/types.gen" -import { sendMessage } from "@/pages/Session" +import { sendMessage } from "@/context/SessionContext" import { useCommitteeStore } from "@/store/useCommitteeStore" diff --git a/frontend/src/components/session/bottom-bar-buttons/VoteButton.tsx b/frontend/src/components/session/bottom-bar-buttons/VoteButton.tsx index 91358dc..2721088 100644 --- a/frontend/src/components/session/bottom-bar-buttons/VoteButton.tsx +++ b/frontend/src/components/session/bottom-bar-buttons/VoteButton.tsx @@ -10,9 +10,17 @@ import { import { Vote } from "lucide-react" import { States } from "@/schemas/types.gen" import { useCommitteeStore } from "@/store/useCommitteeStore" +import { sendMessage } from "@/context/SessionContext" +import { MajorityTypes, ChairEvents, type OpenInformalVotingEvent, type CloseInformalVotingEvent } from "@/schemas/types.gen" +import { useRef } from "react" export default function VoteButton() { const currentState = useCommitteeStore((state) => state.current_state) + const title = useRef(null) + const majority = useRef(null) + const veto = useRef(null) + + return ( @@ -32,19 +40,19 @@ export default function VoteButton() {
- +

O que vai ser votado

- + + +
- +

@@ -52,7 +60,8 @@ export default function VoteButton() {

- + +
diff --git a/frontend/src/components/session/bottom-bar.tsx b/frontend/src/components/session/bottom-bar.tsx index 634bdf7..ce5503c 100644 --- a/frontend/src/components/session/bottom-bar.tsx +++ b/frontend/src/components/session/bottom-bar.tsx @@ -6,12 +6,14 @@ import SessionButton from "./bottom-bar-buttons/SessionButton" import ExitButton from "./bottom-bar-buttons/ExitButton" import BRBButton from "./bottom-bar-buttons/BRB" import IncidentHelp from "./bottom-bar-buttons/IncidentHelp" - -const isChair = true // Replace with actual logic to determine if the user is the chair +import { useSession } from "@/context/SessionContext" +import { SessionRoles } from "@/schemas/types.gen" export default function BottomBar() { + const {role} = useSession() + const isChair = role===SessionRoles.CHAIR return ( <> diff --git a/frontend/src/components/session/delegation-map.tsx b/frontend/src/components/session/delegation-map.tsx index e4edcdc..7d1e901 100644 --- a/frontend/src/components/session/delegation-map.tsx +++ b/frontend/src/components/session/delegation-map.tsx @@ -15,7 +15,7 @@ import { } from "@/components/ui/context-menu" import { useCommitteeStore } from "@/store/useCommitteeStore" import { CircleFlag } from 'react-circle-flags' -import { sendMessage } from "@/pages/Session" +import { sendMessage } from "@/context/SessionContext" import { type ChairInsertQueueEvent, type MarkRollCallEvent, type SpeakerEvent , ChairEvents, RollCallChoice } from "@/schemas/types.gen" import { Tooltip, diff --git a/frontend/src/components/session/manual-quorum.tsx b/frontend/src/components/session/manual-quorum.tsx index 79a7bea..0fa3589 100644 --- a/frontend/src/components/session/manual-quorum.tsx +++ b/frontend/src/components/session/manual-quorum.tsx @@ -12,7 +12,7 @@ import { import { useCommitteeStore } from "@/store/useCommitteeStore" import { ScrollArea } from "@/components/ui/scroll-area" import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group" -import { sendMessage } from "@/pages/Session" +import { sendMessage } from "@/context/SessionContext" import { Item, ItemContent, diff --git a/frontend/src/components/session/moderated-debate.tsx b/frontend/src/components/session/moderated-debate.tsx index cba4fbe..b5b5b73 100644 --- a/frontend/src/components/session/moderated-debate.tsx +++ b/frontend/src/components/session/moderated-debate.tsx @@ -14,11 +14,15 @@ import { TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip" +import { useSession } from "@/context/SessionContext" +import { SessionRoles } from "@/schemas/types.gen" -const isChair = true // Replace with actual logic to determine if the user is the chair //TODO determine if queue is open, if not obscure the button and show a message that the queue is closed export default function ModeratedDebate() { + + const {role} = useSession() + const isChair = role===SessionRoles.CHAIR const gslQueue = useCommitteeStore((state) => state.gsl_queue ?? []) const currentSpeaker = useCommitteeStore((state) => state.current_speaker) const delegationsById = useCommitteeStore((state) => state.delegations) diff --git a/frontend/src/components/session/motions-list.tsx b/frontend/src/components/session/motions-list.tsx index 093d5dc..b81608c 100644 --- a/frontend/src/components/session/motions-list.tsx +++ b/frontend/src/components/session/motions-list.tsx @@ -12,6 +12,8 @@ import { } from "@/components/ui/item" import { Badge } from "@/components/ui/badge" import Flags from "@/components/ui/flags" +import { useSession } from "@/context/SessionContext" +import { SessionRoles } from "@/schemas/types.gen" export type Motion = { id: string @@ -26,9 +28,11 @@ type MotionsListProps = { motions: Motion[] } -const isChair = true// Replace with actual logic to determine if the user is the chair export default function MotionsList({ motions }: MotionsListProps) { + const {role} = useSession() + const isChair = role===SessionRoles.CHAIR + const toMinutes = (time: string): number => { const [hours, minutes] = time.split(":").map(Number) if (Number.isNaN(hours) || Number.isNaN(minutes)) { diff --git a/frontend/src/components/session/speaker-list.tsx b/frontend/src/components/session/speaker-list.tsx index 8ed929b..33f4eac 100644 --- a/frontend/src/components/session/speaker-list.tsx +++ b/frontend/src/components/session/speaker-list.tsx @@ -15,14 +15,18 @@ import { TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip" -import { sendMessage } from "@/pages/Session" +import { sendMessage } from "@/context/SessionContext" import { type SpeakerEvent, ChairEvents } from "@/schemas/types.gen" +import { useSession } from "@/context/SessionContext" +import { SessionRoles } from "@/schemas/types.gen" -const isChair = true // Replace with actual logic to determine if the user is the chair const isAlredyInQueue = true // Replace with actual logic to determine if the user is already in the queue //TODO determine if queue is open, if not obscure the button and show a message that the queue is closed export default function SpeakerList() { + + const {role} = useSession() + const isChair = role===SessionRoles.CHAIR const gslQueue = useCommitteeStore((state) => state.gsl_queue ?? []) const currentSpeaker = useCommitteeStore((state) => state.current_speaker) const delegationsById = useCommitteeStore((state) => state.delegations) diff --git a/frontend/src/components/session/timer.tsx b/frontend/src/components/session/timer.tsx index 33fb4ae..a09c441 100644 --- a/frontend/src/components/session/timer.tsx +++ b/frontend/src/components/session/timer.tsx @@ -3,14 +3,16 @@ import { Separator } from "@/components/ui/separator" import { Flag, Pause, Plus, Play } from "lucide-react" import Flags from "@/components/ui/flags" import { useCommitteeStore } from "@/store/useCommitteeStore" -import { sendMessage } from "@/pages/Session" +import { sendMessage } from "@/context/SessionContext" import { type IncreaseTimerEvent, type ToggleTimerEvent, ChairEvents } from "@/schemas/types.gen" import { useEffect, useState } from "react" - -const isChair = true // Replace with actual logic to determine if the user is the chair +import { useSession } from "@/context/SessionContext" +import { SessionRoles } from "@/schemas/types.gen" export default function Timer() { + const {role} = useSession() + const isChair = role===SessionRoles.CHAIR const delegations = useCommitteeStore((state) => state.delegations); const currentSpeaker = useCommitteeStore((state) => state.current_speaker); const speaker = currentSpeaker !== null && currentSpeaker !== undefined ? delegations[currentSpeaker] : { diff --git a/frontend/src/components/session/voting-popup.tsx b/frontend/src/components/session/voting-popup.tsx index 8399533..a8038b2 100644 --- a/frontend/src/components/session/voting-popup.tsx +++ b/frontend/src/components/session/voting-popup.tsx @@ -15,21 +15,31 @@ import { FieldLabel, } from "@/components/ui/field" import { useState } from "react" +import { useCommitteeStore } from "@/store/useCommitteeStore" +import { sendMessage } from "@/context/SessionContext" +import { DelegateEvents, VotingChoice, type CastVoteEvent } from "@/schemas/types.gen" type VoteType = "rollCall1" | "rollCall2" | "procedural" | "informal" -const voteType: VoteType = "procedural" -const voteTitle = "Batata no Coffee Breaks" const canAbstain = false +const voteType : VoteType = "procedural" + export default function VotingPopup() { - const [voteWithRights, setVoteWithRights] = useState(false) + + //TODO: Implement rollcall voting const isRollCall1 = voteType === "rollCall1" const isRollCall2 = voteType === "rollCall2" const isRollCall = isRollCall1 || isRollCall2 + const [voteWithRights, setVoteWithRights] = useState(false) + //TEMPORARY^^^^ + + const voting = useCommitteeStore((state) => state.voting ?? null) + const voteTitle = voting?.title + const [voted, setVoted] = useState(false) return ( - + Votação @@ -66,8 +76,12 @@ export default function VotingPopup() {
- + )} - +
diff --git a/frontend/src/context/SessionContext.tsx b/frontend/src/context/SessionContext.tsx new file mode 100644 index 0000000..8027598 --- /dev/null +++ b/frontend/src/context/SessionContext.tsx @@ -0,0 +1,96 @@ +import { useContext, type ReactNode } from 'react'; +import { createContext, useEffect, useState } from 'react'; +import { useAuth } from './AuthContext'; +import { useParams } from 'react-router-dom'; +import { UpdateStore } from '@/store/useCommitteeStore'; +import {type SessionRepresentation,type BodyDummyCommitteesDummyGet as Types} from '@/schemas/types.gen'; + + +interface SessionContextType{ + role: string + representation_id: number | null +} + +const SessionContext = createContext({ + role: "", + representation_id: null, +}); + +let socket : WebSocket|null = null + +export function SessionProvider({ children }: { children: ReactNode }) +{ + const { token } = useAuth() + const { sessionId } = useParams<{ sessionId: string }>(); + const parsedSessionId = Number(sessionId); + + const [, setStatus] = useState("Connecting..."); + + useEffect(() => { + if (!token || !Number.isInteger(parsedSessionId) || parsedSessionId < 1) { + return; + } + + const ws = new WebSocket( + `${import.meta.env.VITE_WS_URL}/ws/${parsedSessionId}`, + ); + socket = ws + + ws.onopen = () => { + ws?.send(JSON.stringify({ access_token: token })); + setStatus("Connected"); + } + + ws.onmessage = (event) => { + const data = JSON.parse(event.data); + console.log(data); + UpdateStore(data); + }; + + ws.onclose = () => setStatus("Disconnected"); + + return () => { + ws.close(); + if (socket === ws) socket = null; + } + }, [parsedSessionId, token]); + + const [role, setRole] = useState("") + const [representation_id, setRepresentation_id] = useState(null) + + useEffect(() => { + fetch(`${import.meta.env.VITE_API_URL}/access/sessions/${parsedSessionId}/me`, + { + method:"GET", + headers:{ + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + } + } + ).then((response)=> + { + if(!response.ok) throw new Error("Error when getting role") + return response.json() + }).then((data : SessionRepresentation) => {setRole(data.role); setRepresentation_id(data.representation_id);}) + }, [parsedSessionId, token]) + + const value ={ + role: role, + representation_id: representation_id + } + + return {children}; +} + +export function sendMessage(data: Types["types"]) { + if (socket && socket.readyState === WebSocket.OPEN) + { + socket.send(JSON.stringify(data)); + } + else + { + console.error("WebSocket is not connected."); + } +} + +export const useSession = () => useContext(SessionContext) \ No newline at end of file diff --git a/frontend/src/pages/Session.tsx b/frontend/src/pages/Session.tsx index a60bb7b..456e219 100644 --- a/frontend/src/pages/Session.tsx +++ b/frontend/src/pages/Session.tsx @@ -1,7 +1,7 @@ -import { useEffect, useState} from 'react'; import { Navigate, useParams } from 'react-router-dom'; import { useAuth } from '@/context/AuthContext'; -import { UpdateStore, useCommitteeStore } from '../store/useCommitteeStore.ts' +import { useCommitteeStore } from '../store/useCommitteeStore.ts' +import { SessionProvider } from '@/context/SessionContext.tsx'; import MotionsList from "@/components/session/motions-list" import SpeakerList from "@/components/session/speaker-list" //import ModeratedDebate from "@/components/session/moderated-debate" @@ -10,30 +10,9 @@ import BottomBar from "@/components/session/bottom-bar" import TopBar from '@/components/session/top-bar'; import DelegationMap from '@/components/session/delegation-map'; import VotingPopup from '@/components/session/voting-popup.tsx'; -import {type BodyDummyCommitteesDummyGet as Types} from '@/schemas/types.gen'; - -let socket : WebSocket | null = null; - -/* -Use this function to send events to the backend, -any data with one of the Event types in schemas/types.gen.ts should work -*/ -export function sendMessage(data: Types["types"]) { - if (socket && socket.readyState === WebSocket.OPEN) - { - socket.send(JSON.stringify(data)); - } - else - { - console.error("WebSocket is not connected."); - } -} - export default function SessionPage() { - const { loading, token } = useAuth() - const motions = [ { id: "motion-1", @@ -61,67 +40,21 @@ export default function SessionPage() { }, ] + const {loading, token } = useAuth() // id that matches the name given in the Route path, at App.tsx const { sessionId } = useParams<{ sessionId: string }>(); const parsedSessionId = Number(sessionId); - //const {start_time} = useCommitteeStore(); const all = useCommitteeStore(); - const [, setStatus] = useState("Connecting..."); - //const [, setUptime] = useState(0); - - useEffect(() => { - if (!token || !Number.isInteger(parsedSessionId) || parsedSessionId < 1) { - return; - } - - const ws = new WebSocket( - `${import.meta.env.VITE_WS_URL}/ws/${parsedSessionId}`, - ); - socket = ws; - - ws.onopen = () => { - ws?.send(JSON.stringify({ access_token: token })); - setStatus("Connected"); - } - - ws.onmessage = (event) => { - const data = JSON.parse(event.data); - console.log(data); - UpdateStore(data); - }; - - ws.onclose = () => setStatus("Disconnected"); - - return () => { - ws.close(); - if (socket === ws) socket = null; - } - }, [parsedSessionId, token]); - if (loading) return

Loading session…

; if (!token) return ; if (!Number.isInteger(parsedSessionId) || parsedSessionId < 1) { return

Invalid session ID.

; } - // Useeffect for local uptime timer - /*useEffect(() => { - - if (!start_time) return; - - // calculate timer - const interval = setInterval(() => { - const start = new Date(start_time).getTime(); - const now = new Date().getTime(); - setUptime(Math.floor((now - start) / 1000)); - }, 1000); - - - return () => clearInterval(interval); - }, [start_time]);*/ console.log(all); return ( +
{/*

{status} @@ -150,6 +83,6 @@ export default function SessionPage() {

- +
); } diff --git a/frontend/src/schemas/types.gen.ts b/frontend/src/schemas/types.gen.ts index a945d29..c7849ba 100644 --- a/frontend/src/schemas/types.gen.ts +++ b/frontend/src/schemas/types.gen.ts @@ -65,16 +65,6 @@ export type CastVoteEvent = { payload: DelegateVotingPayload; }; -/** - * ChairCloseInformalVotingPayload - */ -export type ChairCloseInformalVotingPayload = { - /** - * Voting Id - */ - voting_id?: number | null; -}; - /** * ChairEvents */ @@ -157,10 +147,7 @@ export type ChairOpenInformalVotingPayload = { * Title */ title?: string | null; - /** - * Majority - */ - majority: 'SIMPLE' | 'QUALIFIED' | 'ABSOLUTE'; + majority: MajorityTypes; /** * Veto Power */ @@ -178,7 +165,7 @@ export type ChairResolveMotionPayload = { /** * Action */ - action: 'ACCEPT' | 'DENY'; + action: boolean; }; /** @@ -237,7 +224,7 @@ export type CloseInformalVotingEvent = { * Type */ type: 'CloseInformalVotingEvent'; - payload: ChairCloseInformalVotingPayload; + payload: EmptyPayload; }; /** @@ -381,22 +368,7 @@ export type DelegateQuestionPayload = { * DelegateVotingPayload */ export type DelegateVotingPayload = { - /** - * Type - */ - type: 'FORMAL' | 'INFORMAL'; - /** - * Motion Id - */ - motion_id?: number | null; - /** - * Title - */ - title?: string | null; - /** - * Vote - */ - vote: 'FAVOUR' | 'AGAINST' | 'ABSTAIN'; + vote: VotingChoice; }; /** @@ -502,6 +474,20 @@ export type LeaveQueueEvent = { }; }; +/** + * MajorityTypes + */ +export const MajorityTypes = { + MAIORIA_SIMPLES: 'Maioria Simples', + MAIORIA_QUALIFICADA: 'Maioria Qualificada', + CONSENSO: 'Consenso' +} as const; + +/** + * MajorityTypes + */ +export type MajorityTypes = typeof MajorityTypes[keyof typeof MajorityTypes]; + /** * MarkAgendaItemEvent */ @@ -834,6 +820,27 @@ export type SessionLiveState = { roll_call: RollCallContext; }; +/** + * SessionRepresentation + */ +export type SessionRepresentation = { + role: SessionRoles; + /** + * Representation Id + */ + representation_id: number | null; +}; + +/** + * SessionRoles + */ +export const SessionRoles = { CHAIR: 'chair', DELEGATE: 'delegate' } as const; + +/** + * SessionRoles + */ +export type SessionRoles = typeof SessionRoles[keyof typeof SessionRoles]; + /** * SetAgendaEvent */ @@ -976,14 +983,25 @@ export type ValidationError = { }; }; +/** + * VotingChoice + */ +export const VotingChoice = { + FAVOUR: 'Favour', + AGAINST: 'Against', + ABSTAIN: 'Abstain' +} as const; + +/** + * VotingChoice + */ +export type VotingChoice = typeof VotingChoice[keyof typeof VotingChoice]; + /** * VotingContext */ export type VotingContext = { - /** - * Target Type - */ - target_type: 'PROCEDURAL' | 'SUBSTANTIVE' | 'INFORMAL'; + target_type: VotingType; motion_in_vote?: MotionContext | null; /** * Title @@ -994,18 +1012,29 @@ export type VotingContext = { * Voting Registry */ voting_registry?: { - [key: string]: 'FAVOUR' | 'AGAINST' | 'ABSTAIN'; + [key: string]: VotingChoice; }; - /** - * Majority - */ - majority: 'SIMPLE' | 'QUALIFIED' | 'ABSOLUTE'; + majority: MajorityTypes; /** * Veto Power */ veto_power: boolean; }; +/** + * VotingType + */ +export const VotingType = { + INFORMAL: 'Informal', + PROCEDURAL: 'Procedural', + SUBSTANTIVE: 'Substantive' +} as const; + +/** + * VotingType + */ +export type VotingType = typeof VotingType[keyof typeof VotingType]; + export type DummyCommitteesDummyGetData = { body: BodyDummyCommitteesDummyGet; path?: never; @@ -1121,5 +1150,7 @@ export type GetMySessionAccessAccessSessionsSessionIdMeGetResponses = { /** * Successful Response */ - 200: unknown; + 200: SessionRepresentation; }; + +export type GetMySessionAccessAccessSessionsSessionIdMeGetResponse = GetMySessionAccessAccessSessionsSessionIdMeGetResponses[keyof GetMySessionAccessAccessSessionsSessionIdMeGetResponses];