/** * Votes store: vote sessions, individual votes, and result computation. * * Maps to the backend /api/v1/votes endpoints. */ export interface Vote { id: string session_id: string voter_id: string vote_value: string nuanced_level: number | null comment: string | null signature: string signed_payload: string voter_wot_status: string voter_is_smith: boolean voter_is_techcomm: boolean is_active: boolean created_at: string } export interface VoteSession { id: string decision_id: string | null item_version_id: string | null voting_protocol_id: string wot_size: number smith_size: number techcomm_size: number starts_at: string ends_at: string status: string votes_for: number votes_against: number votes_total: number smith_votes_for: number techcomm_votes_for: number threshold_required: number result: string | null chain_recorded: boolean chain_tx_hash: string | null created_at: string } export interface VoteResult { session_id: string status: string votes_for: number votes_against: number votes_total: number wot_size: number smith_size: number techcomm_size: number smith_votes_for: number techcomm_votes_for: number threshold_required: number result: string smith_threshold: number | null smith_pass: boolean techcomm_threshold: number | null techcomm_pass: boolean } export interface ThresholdDetails { wot_threshold: number smith_threshold: number | null techcomm_threshold: number | null wot_pass: boolean smith_pass: boolean | null techcomm_pass: boolean | null inertia_factor: number required_ratio: number } export interface VoteCreate { session_id: string vote_value: string nuanced_level?: number | null comment?: string | null signature: string signed_payload: string } export interface VoteSessionCreate { decision_id?: string | null item_version_id?: string | null voting_protocol_id: string wot_size?: number smith_size?: number techcomm_size?: number } export interface SessionFilters { status?: string voting_protocol_id?: string decision_id?: string } interface VotesState { currentSession: VoteSession | null votes: Vote[] result: VoteResult | null thresholdDetails: ThresholdDetails | null sessions: VoteSession[] loading: boolean error: string | null } export const useVotesStore = defineStore('votes', { state: (): VotesState => ({ currentSession: null, votes: [], result: null, thresholdDetails: null, sessions: [], loading: false, error: null, }), getters: { isSessionOpen: (state): boolean => { if (!state.currentSession) return false return state.currentSession.status === 'open' && new Date(state.currentSession.ends_at) > new Date() }, participationRate: (state): number => { if (!state.currentSession || state.currentSession.wot_size === 0) return 0 return (state.currentSession.votes_total / state.currentSession.wot_size) * 100 }, forPercentage: (state): number => { if (!state.currentSession || state.currentSession.votes_total === 0) return 0 return (state.currentSession.votes_for / state.currentSession.votes_total) * 100 }, }, actions: { /** * Fetch a vote session by ID with its votes and result. */ async fetchSession(sessionId: string) { this.loading = true this.error = null try { const { $api } = useApi() const [session, votes, result] = await Promise.all([ $api(`/votes/sessions/${sessionId}`), $api(`/votes/sessions/${sessionId}/votes`), $api(`/votes/sessions/${sessionId}/result`), ]) this.currentSession = session this.votes = votes this.result = result } catch (err: any) { this.error = err?.data?.detail || err?.message || 'Session de vote introuvable' } finally { this.loading = false } }, /** * Submit a vote to the current session. */ async submitVote(payload: VoteCreate) { this.loading = true this.error = null try { const { $api } = useApi() const vote = await $api(`/votes/sessions/${payload.session_id}/vote`, { method: 'POST', body: payload, }) // Update local state this.votes.push(vote) // Refresh session tallies and result if (this.currentSession) { const [session, result] = await Promise.all([ $api(`/votes/sessions/${payload.session_id}`), $api(`/votes/sessions/${payload.session_id}/result`), ]) this.currentSession = session this.result = result } return vote } catch (err: any) { this.error = err?.data?.detail || err?.message || 'Erreur lors du vote' throw err } finally { this.loading = false } }, /** * Fetch threshold details for a session. */ async fetchThresholdDetails(sessionId: string) { this.loading = true this.error = null try { const { $api } = useApi() this.thresholdDetails = await $api( `/votes/sessions/${sessionId}/threshold`, ) } catch (err: any) { this.error = err?.data?.detail || err?.message || 'Erreur lors du chargement des details du seuil' } finally { this.loading = false } }, /** * Close a vote session. */ async closeSession(sessionId: string) { this.loading = true this.error = null try { const { $api } = useApi() const session = await $api(`/votes/sessions/${sessionId}/close`, { method: 'POST', }) this.currentSession = session // Refresh result after closing this.result = await $api(`/votes/sessions/${sessionId}/result`) } catch (err: any) { this.error = err?.data?.detail || err?.message || 'Erreur lors de la fermeture de la session' throw err } finally { this.loading = false } }, /** * Fetch a list of vote sessions with optional filters. */ async fetchSessions(filters?: SessionFilters) { this.loading = true this.error = null try { const { $api } = useApi() const query: Record = {} if (filters?.status) query.status = filters.status if (filters?.voting_protocol_id) query.voting_protocol_id = filters.voting_protocol_id if (filters?.decision_id) query.decision_id = filters.decision_id this.sessions = await $api('/votes/sessions', { query }) } catch (err: any) { this.error = err?.data?.detail || err?.message || 'Erreur lors du chargement des sessions' } finally { this.loading = false } }, /** * Create a new vote session. */ async createSession(data: VoteSessionCreate) { this.loading = true this.error = null try { const { $api } = useApi() const session = await $api('/votes/sessions', { method: 'POST', body: data, }) this.sessions.push(session) return session } catch (err: any) { this.error = err?.data?.detail || err?.message || 'Erreur lors de la creation de la session' throw err } finally { this.loading = false } }, /** * Clear the current session state. */ clearSession() { this.currentSession = null this.votes = [] this.result = null this.thresholdDetails = null }, }, })