Skip to content

Commit

Permalink
Merge pull request #34 from NUSComputingDev/KAN-28-Elections
Browse files Browse the repository at this point in the history
Add election results
  • Loading branch information
Respirayson authored Sep 1, 2024
2 parents 988cf1c + 5aa4320 commit e50623c
Show file tree
Hide file tree
Showing 7 changed files with 467 additions and 0 deletions.
32 changes: 32 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@babel/plugin-syntax-import-attributes": "^7.23.3",
"@headlessui/react": "^2.1.2",
"@heroicons/react": "^1.0.6",
"@radix-ui/react-slot": "^1.1.0",
"clsx": "^2.1.1",
"comclub-website-2024": "file:",
"date-fns": "^3.6.0",
Expand Down
79 changes: 79 additions & 0 deletions src/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as React from 'react';

import { cn } from '../../lib/utils';

const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
'rounded-lg border bg-card text-card-foreground shadow-sm',
className,
)}
{...props}
/>
));
Card.displayName = 'Card';

const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('flex flex-col space-y-1.5 p-6', className)}
{...props}
/>
));
CardHeader.displayName = 'CardHeader';

const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
'text-2xl font-semibold leading-none tracking-tight',
className,
)}
{...props}
/>
));
CardTitle.displayName = 'CardTitle';

const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
));
CardDescription.displayName = 'CardDescription';

const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
));
CardContent.displayName = 'CardContent';

const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('flex items-center p-6 pt-0', className)}
{...props}
/>
));
CardFooter.displayName = 'CardFooter';

export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
117 changes: 117 additions & 0 deletions src/components/Table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import * as React from 'react';

import { cn } from '../../lib/utils';

const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className='relative w-full overflow-auto'>
<table
ref={ref}
className={cn('w-full caption-bottom text-sm', className)}
{...props}
/>
</div>
));
Table.displayName = 'Table';

const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />
));
TableHeader.displayName = 'TableHeader';

const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn('[&_tr:last-child]:border-0', className)}
{...props}
/>
));
TableBody.displayName = 'TableBody';

const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',
className,
)}
{...props}
/>
));
TableFooter.displayName = 'TableFooter';

const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
'border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',
className,
)}
{...props}
/>
));
TableRow.displayName = 'TableRow';

const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
'h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0',
className,
)}
{...props}
/>
));
TableHead.displayName = 'TableHead';

const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn('p-4 align-middle [&:has([role=checkbox])]:pr-0', className)}
{...props}
/>
));
TableCell.displayName = 'TableCell';

const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn('mt-4 text-sm text-muted-foreground', className)}
{...props}
/>
));
TableCaption.displayName = 'TableCaption';

export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
};
2 changes: 2 additions & 0 deletions src/pages/elections/Candidates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { AnimatePresence, motion } from 'framer-motion';
import { useOutsideClick } from '../../../hooks/use-outside-click';
import WindowCard from '../../layout/WindowCard';
import { cards } from './constants';
import Results from './Results';

export function Candidates() {
const [active, setActive] = useState<(typeof cards)[number] | boolean | null>(
Expand Down Expand Up @@ -86,6 +87,7 @@ export function Candidates() {

return (
<section className='elections h-full gap-4'>
<Results />
<h1 className='title'>Candidates</h1>
<p className='text-neutral-500 text-xl text-center'>
See the candidates running for the NUS Students&apos; Computing Club
Expand Down
121 changes: 121 additions & 0 deletions src/pages/elections/Results.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
} from '../../components/Card';
import {
Table,
TableHeader,
TableRow,
TableHead,
TableBody,
TableCell,
} from '../../components/Table';
import React from 'react';
import { electionsResults } from './constants';

export default function Results() {
return (
<div className='flex flex-col w-full space-y-4'>
<main className='grid min-h-[calc(100vh_-_theme(spacing.16))] grid-cols-1 gap-4 p-4 md:gap-8 md:p-10'>
<Card>
<CardHeader className='flex flex-row items-center justify-between pb-2 space-y-0'>
<div className='flex items-center space-x-2'>
<div className='flex flex-col'>
<CardTitle className='text-base font-bold'>
2024 Computing Club Election Results
</CardTitle>
<CardDescription className='text-sm'>
Results of the recent election
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className='flex flex-col gap-4'>
<div className='grid grid-cols-1 gap-4 md:grid-cols-2'>
<Card>
<CardHeader className='flex flex-row items-center justify-between pb-2 space-y-0'>
<CardTitle className='text-sm font-medium'>
Total Votes
</CardTitle>
<UsersIcon className='w-4 h-4 text-gray-500 dark:text-gray-400' />
</CardHeader>
<CardContent>
<div className='text-2xl font-bold'>354</div>
</CardContent>
</Card>
<Card>
<CardHeader className='flex flex-row items-center justify-between pb-2 space-y-0'>
<CardTitle className='text-sm font-medium'>
Total Candidates
</CardTitle>
<UsersIcon className='w-4 h-4 text-gray-500 dark:text-gray-400' />
</CardHeader>
<CardContent>
<div className='text-2xl font-bold'>14</div>
</CardContent>
</Card>
</div>
</CardContent>
</Card>
<Card>
<CardHeader className='flex flex-row items-center justify-between pb-2 space-y-0'>
<CardTitle className='text-base font-bold'>Results</CardTitle>
</CardHeader>
<CardContent className='p-2'>
<Table className='w-full table table-auto'>
<TableHeader>
<TableRow>
<TableHead>Rank</TableHead>
<TableHead>Candidate</TableHead>
<TableHead>For</TableHead>
<TableHead>Abstain</TableHead>
<TableHead>Against</TableHead>
<TableHead>Percentage</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{electionsResults.sort((a, b) => ('' + a.name).localeCompare(b.name)).map((result, index) => (
<TableRow key={index}>
<TableCell className='font-semibold'>
{index + 1}
</TableCell>
<TableCell>{result.name}</TableCell>
<TableCell>{result.for}</TableCell>
<TableCell>{result.abstain}</TableCell>
<TableCell>{result.against}</TableCell>
<TableCell className={`${result.final >= 50 ? 'text-green-500' : 'text-red-500'} font-semibold`}>{result.final}%</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
</main>
</div>
);
}

function UsersIcon(props) {
return (
<svg
{...props}
xmlns='http://www.w3.org/2000/svg'
width='24'
height='24'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
>
<path d='M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2' />
<circle cx='9' cy='7' r='4' />
<path d='M22 21v-2a4 4 0 0 0-3-3.87' />
<path d='M16 3.13a4 4 0 0 1 0 7.75' />
</svg>
);
}
Loading

0 comments on commit e50623c

Please sign in to comment.