From 4c4f2e720d52597b2a8f271ff2aa689416787cdd Mon Sep 17 00:00:00 2001 From: MartinBraquet Date: Sun, 26 Oct 2025 10:51:52 +0100 Subject: [PATCH] Add status on proposals --- backend/api/src/create-vote.ts | 1 + backend/supabase/vote_results.sql | 3 +++ backend/supabase/votes.sql | 3 ++- common/src/supabase/schema.ts | 4 ++++ common/src/votes/constants.ts | 23 +++++++++++++++++-- web/components/votes/vote-buttons.tsx | 5 ++-- web/components/votes/vote-item.tsx | 33 ++++++++++++++++++--------- 7 files changed, 56 insertions(+), 16 deletions(-) diff --git a/backend/api/src/create-vote.ts b/backend/api/src/create-vote.ts index 42c0741..ffddee5 100644 --- a/backend/api/src/create-vote.ts +++ b/backend/api/src/create-vote.ts @@ -18,6 +18,7 @@ export const createVote: APIHandler< title, description, is_anonymous: isAnonymous, + status: 'voting_open', }) ) diff --git a/backend/supabase/vote_results.sql b/backend/supabase/vote_results.sql index eea0feb..4e566c4 100644 --- a/backend/supabase/vote_results.sql +++ b/backend/supabase/vote_results.sql @@ -44,6 +44,7 @@ create or replace function get_votes_with_results(order_by text default 'recent' created_time timestamptz, creator_id TEXT, is_anonymous boolean, + status text, votes_for int, votes_against int, votes_abstain int, @@ -58,6 +59,7 @@ with results as ( v.created_time, v.creator_id, v.is_anonymous, + v.status, COALESCE(SUM(CASE WHEN r.choice = 1 THEN 1 ELSE 0 END), 0) AS votes_for, COALESCE(SUM(CASE WHEN r.choice = -1 THEN 1 ELSE 0 END), 0) AS votes_against, COALESCE(SUM(CASE WHEN r.choice = 0 THEN 1 ELSE 0 END), 0) AS votes_abstain, @@ -73,6 +75,7 @@ SELECT created_time, creator_id, is_anonymous, + status, votes_for, votes_against, votes_abstain, diff --git a/backend/supabase/votes.sql b/backend/supabase/votes.sql index b5a6d2f..0a2c056 100644 --- a/backend/supabase/votes.sql +++ b/backend/supabase/votes.sql @@ -4,7 +4,8 @@ CREATE TABLE IF NOT EXISTS votes ( creator_id TEXT NOT NULL, title TEXT NOT NULL, is_anonymous BOOLEAN NOT NULL, - description JSONB + description JSONB, + status TEXT ); -- Foreign Keys diff --git a/common/src/supabase/schema.ts b/common/src/supabase/schema.ts index 55fefad..bb51d04 100644 --- a/common/src/supabase/schema.ts +++ b/common/src/supabase/schema.ts @@ -945,6 +945,7 @@ export type Database = { description: Json | null id: number is_anonymous: boolean | null + status: string | null title: string } Insert: { @@ -953,6 +954,7 @@ export type Database = { description?: Json | null id?: never is_anonymous?: boolean | null + status?: string | null title: string } Update: { @@ -961,6 +963,7 @@ export type Database = { description?: Json | null id?: never is_anonymous?: boolean | null + status?: string | null title?: string } Relationships: [ @@ -1009,6 +1012,7 @@ export type Database = { id: number is_anonymous: boolean priority: number + status: string title: string votes_abstain: number votes_against: number diff --git a/common/src/votes/constants.ts b/common/src/votes/constants.ts index 2aaf1de..17d5808 100644 --- a/common/src/votes/constants.ts +++ b/common/src/votes/constants.ts @@ -1,7 +1,26 @@ export const ORDER_BY = ['recent', 'mostVoted', 'priority'] as const export type OrderBy = typeof ORDER_BY[number] -export const Constants: Record = { +export const ORDER_BY_CHOICES: Record = { recent: 'Most recent', mostVoted: 'Most voted', priority: 'Highest Priority', -} \ No newline at end of file +} + +export const STATUS_CHOICES: Record = { + draft: "Draft", + under_review: "Under Review", + voting_open: "Voting Open", + voting_closed: "Voting Closed", + accepted: "Accepted", + pending: "Pending Implementation", + implemented: "Implemented ✔️", + rejected: "Rejected ❌", + cancelled: "Cancelled 🚫", + superseded: "Superseded", + expired: "Expired ⌛", + archived: "Archived", +} + +export const REVERSED_STATUS_CHOICES: Record = Object.fromEntries( + Object.entries(STATUS_CHOICES).map(([key, value]) => [value, key]) +) \ No newline at end of file diff --git a/web/components/votes/vote-buttons.tsx b/web/components/votes/vote-buttons.tsx index 010f10a..dd7b3ba 100644 --- a/web/components/votes/vote-buttons.tsx +++ b/web/components/votes/vote-buttons.tsx @@ -42,13 +42,14 @@ export function VoteButtons(props: { counts: { for: number; abstain: number; against: number } onVoted?: () => void | Promise className?: string + disabled?: boolean }) { + const {voteId, counts, onVoted, className, disabled: disabledProp} = props const user = useUser() - const {voteId, counts, onVoted, className} = props const [loading, setLoading] = useState(null) const [showPriority, setShowPriority] = useState(false) const containerRef = useRef(null) - const disabled = loading !== null + const disabled = disabledProp || loading !== null // Close the dropdown when clicking outside or pressing Escape useEffect(() => { diff --git a/web/components/votes/vote-item.tsx b/web/components/votes/vote-item.tsx index f14a6bf..2d10258 100644 --- a/web/components/votes/vote-item.tsx +++ b/web/components/votes/vote-item.tsx @@ -7,12 +7,14 @@ import {VoteButtons} from 'web/components/votes/vote-buttons' import {getVoteCreator} from "web/lib/supabase/votes"; import {useEffect, useState} from "react"; import Link from "next/link"; +import {STATUS_CHOICES} from "common/votes/constants"; export type Vote = rowFor<'votes'> & { votes_for: number votes_against: number votes_abstain: number priority: number + status?: string } export function VoteItem(props: { @@ -24,7 +26,7 @@ export function VoteItem(props: { useEffect(() => { getVoteCreator(vote.creator_id).then(setCreator) }, [vote.creator_id]) - // console.debug('creator', creator) + // console.debug('creator', creator, vote) return ( @@ -35,19 +37,28 @@ export function VoteItem(props: { {!!vote.priority ?
Priority: {vote.priority.toFixed(0)}%
:

} - {!vote.is_anonymous && creator?.username && {creator.username}} + {!vote.is_anonymous && creator?.username && + {creator.username}}
-
+ + + {vote.status && ( +

+ {STATUS_CHOICES[vote.status]} +

+ )} +
) }