Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: header responsiveness & share integration #31

Merged
merged 11 commits into from
Oct 22, 2024
48 changes: 31 additions & 17 deletions app/allocation/[category]/components/RankingRow.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC, useState } from 'react';
import { FC } from 'react';
import Image from 'next/image';
import { NumericFormat } from 'react-number-format';
import { IProjectRanking } from '@/app/comparison/utils/types';
Expand All @@ -7,17 +7,28 @@ import { ExpandVertical } from '@/public/assets/icon-components/ExpandVertical';
import { LockIcon } from '@/public/assets/icon-components/Lock';
import { UnlockIcon } from '@/public/assets/icon-components/Unlock';
import styles from '@/app/styles/Project.module.css';
// @ts-ignore

import { formatBudget } from '@/app/comparison/utils/helpers';
interface IRankingRowProps {
index: number
project: IProjectRanking
budget: number
locked: boolean
onLock: (id: number) => void
selected: boolean
onSelect: (id: number) => void
onVote: (id: number, share: number) => void
}

const RankingRow: FC<IRankingRowProps> = ({ project, selected, onSelect }) => {
const [value, setValue] = useState(0);

const RankingRow: FC<IRankingRowProps> = ({
index,
project,
budget,
locked,
onLock,
selected,
onSelect,
onVote,
}) => {
const handleAllowdValue = (values: any) => {
const { floatValue } = values;
return !floatValue || floatValue <= 100;
Expand Down Expand Up @@ -54,36 +65,39 @@ const RankingRow: FC<IRankingRowProps> = ({ project, selected, onSelect }) => {
</td>
<td className="pb-8 pl-4 pt-4">
<div className="flex items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-4 py-2">
<p className="text-gray-700">1</p>
<p className="text-gray-700">{index + 1}</p>
</div>
</td>
<td className="relative pb-8 pl-4 pt-4">
<NumericFormat
suffix="%"
decimalScale={2}
value={value}
value={project.share * 100}
onValueChange={(values) => {
setValue(values?.floatValue || 0);
onVote(
project.projectId,
values?.floatValue ? values.floatValue / 100 : 0
);
}}
className="w-24 rounded-md border border-gray-200 bg-gray-50 px-4 py-2 text-center focus:outline-none focus:ring-1"
placeholder="0.00%"
isAllowed={values => handleAllowdValue(values)}
/>
<span className="absolute bottom-2 right-5 text-xs text-gray-400">
235.23
<span className="absolute bottom-2 right-7 text-xs text-gray-400">
{formatBudget(budget)}
</span>
</td>
<td className="pb-8 pt-4">
<button
className={`flex items-center justify-center rounded-md p-2
className={`flex items-center justify-center rounded-md border p-2
${
project.locked
? 'rounded-md border border-[#232634] bg-[#232634]'
: ''
locked
? 'rounded-md border-[#232634] bg-[#232634]'
: 'border-gray-50'
}`}
onClick={() => {}}
onClick={() => onLock(project.projectId)}
>
{project.locked ? <LockIcon color="#fff" /> : <UnlockIcon />}
{locked ? <LockIcon color="#fff" /> : <UnlockIcon />}
</button>
</td>
</tr>
Expand Down
156 changes: 130 additions & 26 deletions app/allocation/[category]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
'use client';

import { useState } from 'react';
import { useParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useParams, useRouter } from 'next/navigation';
import RankingRow from './components/RankingRow';
import HeaderRF6 from '../../comparison/card/Header-RF6';
import Spinner from '@/app/components/Spinner';
import SearchBar from './components/SearchBar';
import { categorySlugIdMap, categoryIdTitleMap } from '../../comparison/utils/helpers';
import {
categorySlugIdMap,
categoryIdTitleMap,
formatBudget,
} from '../../comparison/utils/helpers';
import { Checkbox } from '@/app/utils/Checkbox';
import { LockIcon } from '@/public/assets/icon-components/Lock';
import NotFoundComponent from '@/app/components/404';
import { useProjectsRankingByCategoryId } from '@/app/comparison/utils/data-fetching/ranking';
import {
useProjectsRankingByCategoryId,
useUpdateProjectRanking,
useCategoryRankings,
IProjectRankingObj,
} from '@/app/comparison/utils/data-fetching/ranking';
import { CheckIcon } from '@/public/assets/icon-components/Check';
import { IProjectRanking } from '@/app/comparison/utils/types';
import { ArrowLeft2Icon } from '@/public/assets/icon-components/ArrowLeft2';
import { ArrowRightIcon } from '@/public/assets/icon-components/ArrowRight';

enum VotingStatus {
VOTED,
Expand All @@ -29,16 +41,23 @@ const votingStatusMap = {

const RankingPage = () => {
const params = useParams();
const router = useRouter();

const category = categorySlugIdMap.get((params?.category as string) || '');

const [search, setSearch] = useState<string>('');
const [checkedItems, setCheckedItems] = useState<number[]>([]);
const [projects, setProjects] = useState<IProjectRanking[] | null>(null);
const [rankingArray, setRankingArray] = useState<IProjectRankingObj[]>([]);
const [totalShareError, setTotalShareError] = useState<string | null>(null);
const [lockedItems, setLockedItems] = useState<number[]>([]);

const { data: categoryRankings } = useCategoryRankings();
const { data: ranking, isLoading } = useProjectsRankingByCategoryId(category);
const projects = ranking?.ranking;

console.log(projects);
const { mutate: updateProjectRanking } = useUpdateProjectRanking({
cid: category,
ranking: rankingArray,
});

const handleBulkSelection = () => {
if (!projects) return;
Expand All @@ -51,17 +70,80 @@ const RankingPage = () => {
}
};

const handleVote = (id: number, share: number) => {
if (!projects) return;

const updatedProjects = projects.map(project =>
project.projectId === id ? { ...project, share } : project
);

setProjects(updatedProjects);
};

const handleLocck = (id: number) => {
if (lockedItems.includes(id)) {
setLockedItems(lockedItems.filter(lockedId => lockedId !== id));
}
else {
setLockedItems([...lockedItems, id]);
}
};

const selectItem = (id: number) => {
if (checkedItems.includes(id)) {
setCheckedItems(checkedItems.filter(checkedId => checkedId !== id));
}
else {
setCheckedItems([...checkedItems, id]);
}
};

const submitVotes = () => {
if (!projects) return;

const totalShare = projects.reduce(
(acc, project) => acc + project.share * 100,
0
);

if (totalShare !== 100) {
if (totalShare > 100) {
setTotalShareError(
`Percentages must add up to 100% (remove ${
totalShare - 100
}% from your ballot)`
);
}
else {
setTotalShareError(
`Percentages must add up to 100% (add ${
100 - totalShare
}% to your ballot)`
);
}
return;
}

const rankingArray = projects.map(project => ({
id: project.projectId,
share: project.share,
}));

setRankingArray(rankingArray);

updateProjectRanking();
};

useEffect(() => {
if (ranking) setProjects(ranking?.ranking);
}, [ranking]);

if (!category) return <NotFoundComponent />;

return (
<div>
<HeaderRF6
progress={30}
category="category"
question="Which project had the greatest impact on the OP Stack?"
isFirstSelection={false}
/>
<div className="flex flex-col justify-between gap-4 px-72 py-16">
<HeaderRF6 />
<div className="flex flex-col justify-between gap-4 px-6 py-16 lg:px-20 xl:px-52 2xl:px-72">
<p className="mb-4 text-2xl font-semibold text-gray-700">
Edit your votes
</p>
Expand All @@ -74,7 +156,9 @@ const RankingPage = () => {
<p className="text-sm font-normal text-gray-600">
OP calculations in this ballot are based on your budget of
{' '}
<span className="underline">3,333,333</span>
<span className="underline">
{formatBudget(categoryRankings?.budget)}
</span>
</p>
</div>
<div className="flex items-center justify-center gap-2 rounded-2xl border border-voting-border bg-voting-bg px-3 py-1 text-xs text-voting-text">
Expand Down Expand Up @@ -120,21 +204,17 @@ const RankingPage = () => {
? (
<table className="w-full">
<tbody className="flex flex-col gap-6">
{projects.map(project => (
{projects.map((project, index) => (
<RankingRow
key={project.projectId}
index={index}
budget={(categoryRankings?.budget || 0) * project.share}
project={project}
selected={checkedItems.includes(project.projectId)}
onSelect={(id) => {
if (checkedItems.includes(id)) {
setCheckedItems(
checkedItems.filter(checkedId => checkedId !== id)
);
}
else {
setCheckedItems([...checkedItems, id]);
}
}}
locked={lockedItems.includes(project.projectId)}
onLock={handleLocck}
onSelect={selectItem}
onVote={handleVote}
/>
))}
</tbody>
Expand All @@ -143,6 +223,30 @@ const RankingPage = () => {
: (
<p className="text-center text-gray-400">No projects found</p>
)}

{totalShareError && (
<div className="flex justify-end gap-4">
<p className="text-sm font-medium text-primary">
{totalShareError}
</p>
</div>
)}
<div className="flex justify-between">
<button
className="flex items-center justify-center gap-3 rounded-lg border bg-gray-50 px-4 py-2 font-semibold text-gray-700"
onClick={() => router.push('/allocation')}
>
<ArrowLeft2Icon />
Back to Categories
</button>
<button
className="flex items-center justify-center gap-3 rounded-lg bg-primary px-10 py-2 font-semibold text-white"
onClick={submitVotes}
>
Submit Vote
<ArrowRightIcon size={20} />
</button>
</div>
</div>
</div>
</div>
Expand Down
11 changes: 10 additions & 1 deletion app/allocation/components/CategoryAllocation.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import debounce from 'lodash.debounce';
import Image from 'next/image';
import { ChangeEventHandler, FC, useEffect, useRef } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { roundFractions } from '../utils';
import { useAuth } from '@/app/utils/wallet/AuthProvider';
Expand All @@ -13,6 +14,8 @@ import {
} from '@/app/comparison/utils/types';
import { CheckIcon } from '@/public/assets/icon-components/Check';
import { UserColabGroupIcon } from '@/public/assets/icon-components/UserColabGroup';
import { categoryIdSlugMap } from '@/app/comparison/utils/helpers';

export interface Category {
id: number
imageSrc: string
Expand All @@ -34,6 +37,7 @@ interface CategoryAllocationProps extends Category {
}

const CategoryAllocation: FC<CategoryAllocationProps> = ({
id,
allocatingBudget,
imageSrc,
title,
Expand All @@ -49,6 +53,7 @@ const CategoryAllocation: FC<CategoryAllocationProps> = ({
onPercentageChange,
}) => {
const { isAutoConnecting } = useAuth();
const router = useRouter();

const inputRef = useRef<HTMLInputElement>(null);
const handleInputChange: ChangeEventHandler<HTMLInputElement> = debounce(
Expand Down Expand Up @@ -188,7 +193,11 @@ const CategoryAllocation: FC<CategoryAllocationProps> = ({
: status === CollectionProgressStatusEnum.Finished
? (
<div className="flex w-full flex-col items-center justify-center gap-4">
<button className="flex w-full items-center justify-center gap-2 rounded-md border py-3 font-semibold">
<button
className="flex w-full items-center justify-center gap-2 rounded-md border py-3 font-semibold"
onClick={() =>
router.push(`/allocation/${categoryIdSlugMap.get(id)}`)}
>
Edit
</button>
<div className="flex w-full justify-center gap-2 rounded-xl border border-[#17B26A] bg-[#ECFDF3] py-1">
Expand Down
Loading