Skip to content

Commit c1a414a

Browse files
committed
Make votes sortable
1 parent a574703 commit c1a414a

8 files changed

Lines changed: 93 additions & 29 deletions

File tree

backend/supabase/vote_results.sql

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,15 @@ CREATE INDEX vote_id_idx ON vote_results (vote_id);
3333
DROP INDEX IF EXISTS idx_vote_results_vote_choice;
3434
CREATE INDEX idx_vote_results_vote_choice ON vote_results (vote_id, choice);
3535

36+
DROP INDEX IF EXISTS idx_vote_results_vote_choice_priority;
37+
CREATE INDEX idx_vote_results_vote_choice_priority ON vote_results (vote_id, choice, priority);
38+
39+
DROP INDEX IF EXISTS idx_votes_created_time;
40+
CREATE INDEX idx_votes_created_time ON votes (created_time DESC);
41+
3642

3743
drop function if exists get_votes_with_results;
38-
create or replace function get_votes_with_results()
44+
create or replace function get_votes_with_results(order_by text default 'recent')
3945
returns table (
4046
id BIGINT,
4147
title text,
@@ -49,21 +55,40 @@ create or replace function get_votes_with_results()
4955
priority int
5056
)
5157
as $$
58+
with results as (
59+
SELECT
60+
v.id,
61+
v.title,
62+
v.description,
63+
v.created_time,
64+
v.creator_id,
65+
v.is_anonymous,
66+
COALESCE(SUM(CASE WHEN r.choice = 1 THEN 1 ELSE 0 END), 0) AS votes_for,
67+
COALESCE(SUM(CASE WHEN r.choice = -1 THEN 1 ELSE 0 END), 0) AS votes_against,
68+
COALESCE(SUM(CASE WHEN r.choice = 0 THEN 1 ELSE 0 END), 0) AS votes_abstain,
69+
COALESCE(SUM(r.priority), 0)::float / GREATEST(COALESCE(SUM(CASE WHEN r.choice = 1 THEN 1 ELSE 0 END), 1), 1) * 100 / 3 AS priority
70+
FROM votes v
71+
LEFT JOIN vote_results r ON v.id = r.vote_id
72+
GROUP BY v.id
73+
)
5274
SELECT
53-
v.id,
54-
v.title,
55-
v.description,
56-
v.created_time,
57-
v.creator_id,
58-
v.is_anonymous,
59-
COALESCE(SUM(CASE WHEN r.choice = 1 THEN 1 ELSE 0 END), 0) AS votes_for,
60-
COALESCE(SUM(CASE WHEN r.choice = -1 THEN 1 ELSE 0 END), 0) AS votes_against,
61-
COALESCE(SUM(CASE WHEN r.choice = 0 THEN 1 ELSE 0 END), 0) AS votes_abstain,
62-
coalesce(SUM(r.priority), 0) AS priority
63-
FROM votes v
64-
LEFT JOIN vote_results r ON v.id = r.vote_id
65-
GROUP BY v.id
66-
ORDER BY v.created_time DESC;
75+
id,
76+
title,
77+
description,
78+
created_time,
79+
creator_id,
80+
is_anonymous,
81+
votes_for,
82+
votes_against,
83+
votes_abstain,
84+
priority
85+
FROM results
86+
ORDER BY
87+
CASE WHEN order_by = 'recent' THEN created_time END DESC,
88+
CASE WHEN order_by = 'mostVoted' THEN (votes_for + votes_against + votes_abstain) END DESC,
89+
CASE WHEN order_by = 'mostVoted' THEN created_time END DESC,
90+
CASE WHEN order_by = 'priority' THEN priority END DESC,
91+
CASE WHEN order_by = 'priority' THEN created_time END DESC;
6792
$$ language sql stable;
6893

6994

common/src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ export const pStyle = "mt-1 text-gray-800 dark:text-white whitespace-pre-line";
2020
export const IS_MAINTENANCE = false; // set to true to enable maintenance mode banner
2121

2222
export const MIN_BIO_LENGTH = 250;
23+

common/src/votes/constants.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const ORDER_BY = ['recent', 'mostVoted', 'priority'] as const
2+
export type OrderBy = typeof ORDER_BY[number]
3+
export const Constants: Record<OrderBy, string> = {
4+
recent: 'Most recent',
5+
mostVoted: 'Most voted',
6+
priority: 'Highest Priority',
7+
}

web/components/votes/vote-buttons.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ function VoteButton(props: {
2020
<Button
2121
size="xs"
2222
disabled={disabled}
23-
className={clsx('px-4 py-2 rounded-lg', color)}
23+
className={clsx('px-2 xs:px-4 py-2 rounded-lg', color)}
2424
onClick={onClick}
2525
color={'gray-white'}
2626
>
27-
<div className="font-semibold mx-2">{count}</div>
27+
<div className="font-semibold mx-1 xs:mx-2">{count}</div>
2828
<div className="text-sm">{title}</div>
2929
</Button>
3030
)
@@ -102,7 +102,7 @@ export function VoteButtons(props: {
102102
}
103103

104104
return (
105-
<Row className={clsx('gap-4 mt-2', className)}>
105+
<Row className={clsx('gap-2 xs:gap-4 mt-2 flex-wrap', className)}>
106106
<div className="relative" ref={containerRef}>
107107
<VoteButton
108108
color={clsx('bg-green-700 text-white hover:bg-green-500')}

web/components/votes/vote-info.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ import {Vote, VoteItem} from 'web/components/votes/vote-item'
1616
import Link from "next/link";
1717
import {formLink} from "common/constants";
1818
import { ShowMore } from "../widgets/show-more";
19+
import {ORDER_BY, Constants, OrderBy} from "common/votes/constants";
1920

2021
export function VoteComponent() {
2122
const user = useUser()
23+
const [orderBy, setOrderBy] = useState<OrderBy>('recent')
2224

2325
const {data: votes, refresh: refreshVotes} = useGetter(
2426
'votes',
25-
{},
27+
{orderBy},
2628
getVotes
2729
)
2830

@@ -34,7 +36,24 @@ export function VoteComponent() {
3436

3537
return (
3638
<Col className="mx-2">
37-
<Title className="text-3xl">Proposals</Title>
39+
<Row className="items-center justify-between flex-col xxs:flex-row mb-4 xxs:mb-0 gap-2">
40+
<Title className="text-3xl">Proposals</Title>
41+
<div className="flex items-center gap-2 text-sm justify-end">
42+
<label htmlFor="orderBy" className="text-gray-600">Order by:</label>
43+
<select
44+
id="orderBy"
45+
value={orderBy}
46+
onChange={(e) => setOrderBy(e.target.value as OrderBy)}
47+
className="rounded-md border border-gray-300 px-2 py-1 text-sm bg-canvas-50"
48+
>
49+
{ORDER_BY.map((key) => (
50+
<option key={key} value={key}>
51+
{Constants[key]}
52+
</option>
53+
))}
54+
</select>
55+
</div>
56+
</Row>
3857
<p className={'customlink'}>
3958
You can discuss any of those proposals through the <Link href={'/contact'}>contact form</Link>, the <Link href={formLink}>feedback form</Link>, or any of our <Link href={'/social'}>socials</Link>.
4059
</p>

web/components/votes/vote-item.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ export function VoteItem(props: {
3333
<Col className='text-sm text-gray-500 italic'>
3434
<Content className="w-full" content={vote.description as JSONContent}/>
3535
</Col>
36-
<Row className={'gap-2 mt-2 items-center justify-between w-full customlink'}>
37-
{!!vote.priority ? <div>Priority: {((vote.priority / vote.votes_for / 3) * 100).toFixed(0)}%</div> : <p></p>}
36+
<Row className={'gap-2 mt-2 items-center justify-between w-full customlink flex-wrap'}>
37+
{!!vote.priority ? <div>Priority: {vote.priority.toFixed(0)}%</div> : <p></p>}
3838
{!vote.is_anonymous && creator?.username && <Link href={`/${creator.username}`} className="customlink">{creator.username}</Link>}
3939
</Row>
4040
<VoteButtons

web/lib/supabase/votes.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import {run} from 'common/supabase/utils'
22
import {db} from 'web/lib/supabase/db'
33

4-
export const getVotes = async () => {
5-
const {data, error} = await db.rpc('get_votes_with_results' as any);
6-
if (error) throw error;
4+
import {OrderBy} from "common/votes/constants";
75

8-
// data.forEach((vote: any) => {
9-
// console.log(vote.title, vote.votes_for, vote.votes_against, vote.votes_abstain);
10-
// });
6+
export const getVotes = async (params: { orderBy: OrderBy }) => {
7+
const { orderBy } = params
8+
const {data, error} = await db.rpc('get_votes_with_results' as any, {
9+
order_by: orderBy,
10+
});
11+
if (error) throw error;
1112

1213
return data
1314
}

web/tailwind.config.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,18 @@ module.exports = {
1111
'./components/**/*.{js,ts,jsx,tsx}',
1212
],
1313
theme: {
14-
fontFamily: Object.assign(
14+
15+
screens: {
16+
xxs: '320px',
17+
xs: '480px',
18+
sm: '640px',
19+
md: '768px',
20+
lg: '1024px',
21+
xl: '1280px',
22+
'2xl': '1536px',
23+
},
24+
25+
fontFamily: Object.assign(
1526
{ ...defaultTheme.fontFamily },
1627
{
1728
'major-mono': ['var(--font-logo)', 'monospace'],

0 commit comments

Comments
 (0)