Переглянути джерело

20230307: added dashboard UI

mkbeefcake 1 рік тому
батько
коміт
ca5fc38738
43 змінених файлів з 827 додано та 308 видалено
  1. 27 0
      src/Providers.tsx
  2. 0 23
      src/components/Dashboard/Blocks.tsx
  3. 23 0
      src/components/Dashboard/Channels.tsx
  4. 25 0
      src/components/Dashboard/Election.tsx
  5. 22 77
      src/components/Dashboard/Forum.tsx
  6. 24 0
      src/components/Dashboard/Memberships.tsx
  7. 105 77
      src/components/Dashboard/Proposals.tsx
  8. 10 13
      src/components/Dashboard/Status.tsx
  9. 34 0
      src/components/Dashboard/Validation.tsx
  10. 23 0
      src/components/Dashboard/Videos.tsx
  11. 134 0
      src/components/Dashboard/Workgroup.tsx
  12. 45 109
      src/components/Dashboard/index.tsx
  13. 69 0
      src/components/ui/Banner.tsx
  14. 23 0
      src/components/ui/Line.tsx
  15. 63 0
      src/components/ui/SubBlock.tsx
  16. 76 0
      src/components/ui/TablePaginationActions.tsx
  17. 2 1
      src/config.ts
  18. 0 0
      src/graphtypes/Councilor.ts
  19. 0 0
      src/graphtypes/ElectedCouncil.ts
  20. 0 0
      src/graphtypes/Member.ts
  21. 0 0
      src/graphtypes/Proposal.ts
  22. 0 0
      src/graphtypes/Worker.ts
  23. 0 0
      src/graphtypes/WorkingGroup.ts
  24. 0 0
      src/graphtypes/common/Block.ts
  25. 0 0
      src/graphtypes/common/Dates.ts
  26. 0 0
      src/graphtypes/common/casting.ts
  27. 0 0
      src/graphtypes/common/form.ts
  28. 0 0
      src/graphtypes/common/helpers.ts
  29. 0 0
      src/graphtypes/common/index.ts
  30. 0 0
      src/graphtypes/common/utils.ts
  31. 0 0
      src/graphtypes/index.ts
  32. 15 0
      src/helpers/bn.ts
  33. 2 0
      src/helpers/index.ts
  34. 94 0
      src/helpers/utils.ts
  35. 1 1
      src/hooks/types.ts
  36. 1 1
      src/hooks/useCouncilMembers.ts
  37. 1 1
      src/hooks/useElectedCouncils.ts
  38. 1 1
      src/hooks/useGroupWorkers.ts
  39. 1 1
      src/hooks/useProposals.ts
  40. 1 1
      src/hooks/useWorker.ts
  41. 1 1
      src/hooks/useWorkingGroups.ts
  42. 4 1
      src/index.tsx
  43. 0 0
      src/queries_.ts

+ 27 - 0
src/Providers.tsx

@@ -0,0 +1,27 @@
+import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client';
+import React from 'react';
+import { queryNode } from './config';
+
+const client = new ApolloClient({
+  // uri: import.meta.env.VITE_QN_URL,
+  uri: queryNode,
+  cache: new InMemoryCache(),
+  connectToDevTools: true,
+  defaultOptions: {
+    watchQuery: {
+      fetchPolicy: 'cache-and-network',
+      errorPolicy: 'all',
+    },
+    query: {
+      fetchPolicy: 'standby',
+      errorPolicy: 'all',
+    },
+    mutate: {
+      errorPolicy: 'all',
+    },
+  },
+});
+
+export default function Providers({ children }: React.PropsWithChildren) {
+  return <ApolloProvider client={client}>{children}</ApolloProvider>;
+}

+ 0 - 23
src/components/Dashboard/Blocks.tsx

@@ -1,23 +0,0 @@
-import { Block } from "../../types";
-import moment from "moment";
-
-const Blocks = (props: { blocks: Block[] }) => {
-  const blocks: Block[] = Array.from(new Set(props.blocks))
-    .sort((a: Block, b: Block) => b.id - a.id)
-    .slice(0, 5);
-
-  return (
-    <div className="box overflow-hidden" style={{ height: "13em" }}>
-      <h3>previous blocks</h3>
-      <div>
-        {blocks.map((b) => (
-          <div key={b.id}>
-            {moment(b.timestamp).format("DD/MM/YYYY HH:mm:ss")}: {b.id}
-          </div>
-        ))}
-      </div>
-    </div>
-  );
-};
-
-export default Blocks;

+ 23 - 0
src/components/Dashboard/Channels.tsx

@@ -0,0 +1,23 @@
+import React from "react";
+import SubBlock from "../ui/SubBlock";
+import Line from "../ui/Line";
+import { ElectedCouncil } from "@/queries";
+import { useChannels } from "@/hooks";
+
+const Channels = (props: { council: ElectedCouncil | undefined }) => {
+  const { council } = props;
+  const { created, total, loading, error } = useChannels({ council });
+
+  return (
+    <SubBlock title="Channels">
+      { !loading && (
+        <>
+          <Line content={"Created"} value={created} />
+          <Line content={"Total"} value={total} />
+        </>
+      )}
+    </SubBlock>
+  );
+};
+
+export default Channels;

+ 25 - 0
src/components/Dashboard/Election.tsx

@@ -0,0 +1,25 @@
+import React from "react";
+import SubBlock from "../ui/SubBlock";
+import Line from "../ui/Line";
+import { ElectedCouncil } from "@/queries";
+import { useElection } from "@/hooks";
+import { sumStakes } from "@/helpers";
+
+const Election = (props: { council: ElectedCouncil | undefined}) => {
+  const { council } = props;
+  const { election, loading, error } = useElection({ council });
+
+  return (
+    <SubBlock title="Election">
+      { !loading && (
+        <>
+          <Line content={"Candidates"} value={election? election.candidates.length : "-"} />
+          <Line content={"Votes"} value={election? election.castVotes.length: "-"} />
+          <Line content={"Staked"} value={election? sumStakes(election.candidates).toString().slice(0, length - 10): "-"} />
+        </>
+      )}
+    </SubBlock>
+  );
+};
+
+export default Election;

+ 22 - 77
src/components/Dashboard/Forum.tsx

@@ -1,84 +1,29 @@
 import React from "react";
-import { Link } from "react-router-dom";
-import { LatestPost, Spinner } from "..";
+import SubBlock from "../ui/SubBlock";
+import Line from "../ui/Line";
+import { ElectedCouncil } from "@/queries";
+import { usePostTokenData, useThreadData } from "@/hooks";
 
-import { Post, Thread } from "../../types";
-import {
-  Grid,
-  Paper,
-  makeStyles,
-  Theme,
-  createStyles,
-  Toolbar,
-  AppBar,
-  Typography,
-} from "@material-ui/core";
-
-const useStyles = makeStyles((theme: Theme) =>
-  createStyles({
-    root: {
-      flexGrow: 1,
-      backgroundColor: "#4038FF",
-    },
-    title: {
-      textAlign: "left",
-      flexGrow: 1,
-      padding: "10px",
-      color: "#fff",
-    },
-  })
-);
-
-const Forum = (props: { posts: Post[]; threads: Thread[] }) => {
-  const { handles, posts, threads, startTime } = props;
-  const classes = useStyles();
+const Forum = (props: { council: ElectedCouncil | undefined}) => {
+  const { council } = props;
+  const thread = useThreadData({ council });
+  const post = usePostTokenData({ council });
 
   return (
-    <Grid
-      style={{ textAlign: "center", backgroundColor: "#000", color: "#fff" }}
-      item
-      lg={6}
-      xs={12}
-    >
-      <Paper
-        style={{
-          textAlign: "center",
-          backgroundColor: "#4038FF",
-          color: "#fff",
-          minHeight: 600,
-          maxHeight: 600,
-          overflow: "auto",
-        }}
-      >
-        <AppBar className={classes.root} position="static">
-          <Toolbar>
-            <Typography variant="h5" className={classes.title}>
-              <Link style={{ color: "#fff" }} to={"/forum"}>
-                Posts
-              </Link>
-            </Typography>
-          </Toolbar>
-        </AppBar>
-
-        {posts.length ? (
-          posts
-            .sort((a, b) => b.id - a.id)
-            .slice(0, 10)
-            .map((post) => (
-              <LatestPost
-                key={post.id}
-                selectThread={() => {}}
-                handles={handles}
-                post={post}
-                thread={threads.find((t) => t.id === post.threadId)}
-                startTime={startTime}
-              />
-            ))
-        ) : (
-          <Spinner />
-        )}
-      </Paper>
-    </Grid>
+    <SubBlock title="Forum">
+      { !thread.loading && (
+        <>
+          <Line content={"Threads created"} value={thread.created} />
+          <Line content={"Threads total"} value={thread.total} />
+        </>
+      )}
+      { !post.loading && (
+        <>
+          <Line content={"Posts created"} value={post.created} />
+          <Line content={"Posts total"} value={post.total} />
+        </>
+      )}
+    </SubBlock>
   );
 };
 

+ 24 - 0
src/components/Dashboard/Memberships.tsx

@@ -0,0 +1,24 @@
+import React from "react";
+import SubBlock from "../ui/SubBlock";
+import Line from "../ui/Line";
+import { ElectedCouncil } from "@/graphtypes";
+import { useMemberships } from "@/hooks";
+
+const Memberships = (props: { council: ElectedCouncil | undefined }) => {
+  const { council } = props;
+  const { created, invited, total, loading, error } = useMemberships({ council });
+
+  return (
+    <SubBlock title="Memberships">
+      { !loading && (
+        <>
+          <Line content={"Created"} value={created} />
+          <Line content={"Invited"} value={invited} />
+          <Line content={"Total"} value={total} />
+        </>
+      )}
+    </SubBlock>
+  );
+};
+
+export default Memberships;

+ 105 - 77
src/components/Dashboard/Proposals.tsx

@@ -1,83 +1,111 @@
-import { Link } from "react-router-dom";
-import { ProposalTable } from "..";
-import {
-  createStyles,
-  makeStyles,
-  Grid,
-  Paper,
-  AppBar,
-  Toolbar,
-  Typography,
-  Theme,
-} from "@material-ui/core";
-import { Council, Member, ProposalDetail, Post } from "../../types";
+import React from "react";
+import SubBlock from "../ui/SubBlock";
+import { ElectedCouncil } from "@/queries";
+import { useProposals } from '@/hooks';
+import { Proposal, WorkingGroup } from "../../types";
 
-const useStyles = makeStyles((theme: Theme) =>
-  createStyles({
-    grid: { textAlign: "center", backgroundColor: "#000", color: "#fff" },
-    root: { flexGrow: 1, backgroundColor: "#4038FF" },
-    title: { textAlign: "left", flexGrow: 1 },
-    paper: {
-      textAlign: "center",
-      backgroundColor: "#4038FF",
-      color: "#fff",
-      minHeight: 600,
-      maxHeight: 600,
-      overflow: `hidden`,
-    },
-  })
-);
+import { makeStyles, useTheme, Theme, createStyles } from '@material-ui/core/styles';
+import Table from "@material-ui/core/Table";
+import TableBody from "@material-ui/core/TableBody";
+import TableCell from "@material-ui/core/TableCell";
+import TableContainer from "@material-ui/core/TableContainer";
+import TableHead from "@material-ui/core/TableHead";
+import TableRow from "@material-ui/core/TableRow";
+import TableFooter from '@material-ui/core/TableFooter';
+import TablePagination from '@material-ui/core/TablePagination';
+import TablePaginationActions from "../ui/TablePaginationActions";
 
-const Proposals = (props: {
-  proposals: ProposalDetail[];
-  validators: string[];
-  councils: Council[];
-  members: Member[];
-  posts: Post[];
-  startTime: number;
-  block: number;
-  status: { council: Council };
-  gridSize: GridSize;
-}) => {
-  const classes = useStyles();
-  const { proposals, validators, councils, members, posts, block, status } =
-    props;
-  const pending = proposals.filter((p) => p && p.result === "Pending");
+const useStyles2 = makeStyles({
+	table: {
+		minWidth: 500,
+	}
+})
 
-  return (
-    <Grid className={classes.grid} item lg={props.gridSize} xs={12}>
-      <Paper className={classes.paper}>
-        <AppBar className={classes.root} position="static">
-          <Toolbar>
-            <Typography variant="h6" className={classes.title}>
-              Active Proposals: {pending.length}
-            </Typography>
-            <Link className="m-3 text-light" to={"/proposals"}>
-              All
-            </Link>
-            <Link className="m-3 text-light" to={"/spending"}>
-              Spending
-            </Link>
-            <Link className="m-3 text-light" to={"/councils"}>
-              Votes
-            </Link>
-          </Toolbar>
-        </AppBar>
-        <div className="h-100 overflow-auto">
-          <ProposalTable
-            block={block}
-            hideNav={true}
-            proposals={pending}
-            members={members}
-            council={status.council}
-            councils={councils}
-            posts={posts}
-            status={status}
-            validators={validators}
-          />
-        </div>
-      </Paper>
-    </Grid>
+const ProposalWorker = (props: {proposal: Proposal} ) => {
+	const { proposal } = props;
+
+	return (
+		<TableRow key={`key-${proposal.id}`}>
+			<TableCell>{proposal.title}</TableCell>
+			<TableCell><i>{proposal.createdAt}</i></TableCell>
+			<TableCell>
+				<a
+					href={`https://pioneerapp.xyz/#/proposals/preview/${proposal.id}`}
+					target="_blank"
+					rel="noreferrer"
+					style={{color: 'blue'}}
+				>
+					<i>Link to proposal</i>
+				</a>
+			</TableCell>
+			<TableCell><i>{proposal.status}</i></TableCell>
+		</TableRow>								
+	);
+};
+
+const Proposals = (props: { council: ElectedCouncil | undefined}) => {
+  const { council } = props;
+  const { proposals, loading, error } = useProposals({ council });
+
+	const classes = useStyles2();
+	const [page, setPage] = React.useState(0);
+	const [rowsPerPage, setRowsPerPage] = React.useState(4);
+	
+  const handleChangePage = (event: unknown, newPage: number) => {
+    setPage(newPage);
+  };
+
+  const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
+    setRowsPerPage(parseInt(event.target.value, 10));
+    setPage(0);
+  };
+
+	return (
+    <SubBlock title="Proposals" stretch={6}>
+      { !loading && (
+				<>
+					<TableContainer>
+						<Table aria-label="proposals table">
+							<TableHead>
+								<TableRow>
+									<TableCell>Title</TableCell>
+									<TableCell>Created Date</TableCell>
+									<TableCell>Link</TableCell>
+									<TableCell>Status</TableCell>
+								</TableRow>
+							</TableHead>
+							{proposals && ( <>
+								<TableBody>
+									{rowsPerPage > 0 ? 
+											proposals.slice(page * rowsPerPage, page *rowsPerPage + rowsPerPage)
+												.map((proposal) => <ProposalWorker key={proposal.id} proposal={proposal}/>)
+										: proposals.map((proposal) => <ProposalWorker key={proposal.id} proposal={proposal} />)
+									}
+								</TableBody>
+							</>)}
+							<TableFooter>
+								<TableRow>
+									<TablePagination
+										rowsPerPageOptions={[4]}
+										colSpan={4}
+										count={proposals ? proposals.length : 0}
+										rowsPerPage={rowsPerPage}
+										page={page}
+										onPageChange={handleChangePage}
+										SelectProps={{
+											inputProps: { 'aria-label': 'rows per page' },
+											native: true,
+										}}
+										onRowsPerPageChange={handleChangeRowsPerPage}
+										ActionsComponent={TablePaginationActions}
+									/>
+								</TableRow>
+							</TableFooter>
+						</Table>
+					</TableContainer>
+				</>
+      )}
+    </SubBlock>
   );
 };
 

+ 10 - 13
src/components/Dashboard/Status.tsx

@@ -1,6 +1,4 @@
 import React from "react";
-import { Badge } from "react-bootstrap";
-import { wsLocation } from "../../config";
 
 const Status = (props: {
   connected: boolean,
@@ -8,18 +6,17 @@ const Status = (props: {
   toggleShowStatus: () => void,
 }) => {
   const { toggleShowStatus, connected, fetching } = props;
-  const text = connected
-    ? fetching.length
-      ? `Fetching ${fetching}`
-      : `Connected to ${wsLocation}`
-    : `Connecting to ${wsLocation}`;
+  if (!connected)
+    return (
+      <div className="connecting" onClick={toggleShowStatus}>
+        Connecting ..
+      </div>
+    );
+  if (!fetching?.length) return <div />;
   return (
-    <Badge
-      className={connected ? "connected" : "connecting"}
-      onClick={toggleShowStatus}
-    >
-      {text}
-    </Badge>
+    <div className="connecting" onClick={toggleShowStatus}>
+      Fetching {fetching}
+    </div>
   );
 };
 

+ 34 - 0
src/components/Dashboard/Validation.tsx

@@ -0,0 +1,34 @@
+import React, { useEffect, useState } from "react";
+import SubBlock from "../ui/SubBlock";
+import Line from "../ui/Line";
+import { ElectedCouncil } from "@/queries";
+import { useValidation } from "@/hooks";
+
+const Validation = (props: { council: ElectedCouncil | undefined }) => {
+  const { council } = props;
+  const [validation, setValidation] = useState({count: 0, minted: 0, stakes: 0});
+
+  // const { validator, stake, mint, loading, error } = useValidation({ council });
+  // console.log("validation", validator, stake, mint, loading)
+
+  useEffect(() => {
+    const data = localStorage.getItem("validation");
+    if (!data) return;
+
+    setValidation(JSON.parse(data));
+  }, [])
+
+  return (
+    <SubBlock title="Validation">
+      {(
+        <>
+          <Line content={"Count"} value={validation.count} />
+          <Line content={"Minted"} value={validation.minted} />
+          <Line content={"Staked"} value={validation.stakes} />
+        </>
+      )}
+    </SubBlock>
+  );
+};
+
+export default Validation;

+ 23 - 0
src/components/Dashboard/Videos.tsx

@@ -0,0 +1,23 @@
+import React from "react";
+import SubBlock from "../ui/SubBlock";
+import Line from "../ui/Line";
+import { ElectedCouncil } from "@/queries";
+import { useVideos } from "@/hooks";
+
+const Videos = (props: { council: ElectedCouncil | undefined}) => {
+  const { council } = props;
+  const { created, total, loading, error } = useVideos({ council });
+
+  return (
+    <SubBlock title="Videos">
+      { !loading && (
+        <>
+          <Line content={"Created"} value={created} />
+          <Line content={"Total"} value={total} />
+        </>
+      )}
+    </SubBlock>
+  );
+};
+
+export default Videos;

+ 134 - 0
src/components/Dashboard/Workgroup.tsx

@@ -0,0 +1,134 @@
+import React from "react";
+import SubBlock from "../ui/SubBlock";
+import { ElectedCouncil } from "@/queries";
+import { useWorkingGroups, useWorker } from '@/hooks';
+import { WorkingGroup } from "../../types";
+
+import { makeStyles, useTheme, Theme, createStyles } from '@material-ui/core/styles';
+import Table from "@material-ui/core/Table";
+import TableBody from "@material-ui/core/TableBody";
+import TableCell from "@material-ui/core/TableCell";
+import TableContainer from "@material-ui/core/TableContainer";
+import TableHead from "@material-ui/core/TableHead";
+import TableRow from "@material-ui/core/TableRow";
+import TableFooter from '@material-ui/core/TableFooter';
+import TablePagination from '@material-ui/core/TablePagination';
+import TablePaginationActions from '../ui/TablePaginationActions';
+
+export function GroupWorkers(props: { council: ElectedCouncil, workingGroup : WorkingGroup }) {
+
+	const { council, workingGroup } = props;
+	const { workingTokens, rewardToken, workingTokensReward } = useWorkingGroups({ council });
+	const { exitedWorker, filledWorker, terminatedWorker } = useWorker({ council });
+
+	var token = workingTokens?.filter((data) => workingGroup.name === data.groupId).reduce((a: number, b) => {
+		return a + (b.budgetChangeAmount / 10000000000);
+	}, 0)
+
+	var reward = rewardToken?.filter((data) => workingGroup.name === data.groupId).reduce((a: number, b) => {
+		return a + (b.amount / 10000000000);
+	}, 0)
+
+	var updateReward = workingTokensReward?.filter((data) => workingGroup.name === data.groupId).reduce((a: number, b) => {
+		return a + (b.budgetChangeAmount / 10000000000);
+	}, 0)
+
+	var budget = updateReward! - reward!;
+
+
+	var exited = exitedWorker?.filter(data => workingGroup.name === data.groupId).reduce((a: number, b) => {
+		return isNaN(a + b.worker.length) ? 0 : a + b.worker.length;
+	}, 0)
+
+	var filled = filledWorker?.filter(data => workingGroup.name === data.groupId).reduce((a: number, b) => {
+		return isNaN(a + b.workersHired.length) ? 0 : a + b.workersHired.length;
+	}, 0)
+
+	var terminated = terminatedWorker?.filter(data => workingGroup.name === data.groupId).reduce((a: number, b) => {
+		return isNaN(a + b.worker.length) ? 0 : a + b.worker.length;
+	}, 0)
+
+	var worker = filled! - exited! - terminated!;
+
+	return (
+		<TableRow key={workingGroup.name}>
+			<TableCell>{workingGroup.name}</TableCell>
+			<TableCell><i>{Number.isNaN(worker) ? "-" : worker}</i></TableCell>
+			<TableCell><i>{token?.toFixed(0)}</i></TableCell>
+			<TableCell><i>{budget.toFixed(0)}</i></TableCell>
+		</TableRow>
+	);
+}
+
+const useStyles2 = makeStyles({
+	table: {
+	}
+})
+
+const WorkGroup = (props: { council: ElectedCouncil | undefined}) => {
+  const { council } = props;
+  const { workingGroups, loading, error } = useWorkingGroups({ council });
+
+	const classes = useStyles2();
+	const [page, setPage] = React.useState(0);
+	const [rowsPerPage, setRowsPerPage] = React.useState(4);
+	
+  const handleChangePage = (event: unknown, newPage: number) => {
+    setPage(newPage);
+  };
+
+  const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
+    setRowsPerPage(parseInt(event.target.value, 10));
+    setPage(0);
+  };
+
+	return (
+    <SubBlock title="WorkGroup" stretch={6}>
+      { !loading && (
+				<>
+					<TableContainer>
+						<Table className={classes.table} aria-label="working-group table">
+							<TableHead>
+								<TableRow>
+									<TableCell>Working Groups</TableCell>
+									<TableCell>Workers</TableCell>
+									<TableCell>Minted Tokens during Term</TableCell>
+									<TableCell>Budget/Debt at end of Term</TableCell>
+								</TableRow>
+							</TableHead>
+							{workingGroups && ( 
+								<TableBody>
+									{ rowsPerPage > 0 ? 
+											workingGroups.slice(page * rowsPerPage, page *rowsPerPage + rowsPerPage)
+												.map((workingGroup) => <GroupWorkers key={workingGroup.id} council={council} workingGroup={workingGroup} />)
+										: workingGroups.map((workingGroup) => <GroupWorkers key={workingGroup.id} council={council} workingGroup={workingGroup} />)
+									}
+								</TableBody>
+							)}
+							<TableFooter>
+								<TableRow>
+									<TablePagination
+										rowsPerPageOptions={[4]}
+										colSpan={4}
+										count={workingGroups ? workingGroups.length : 0}
+										rowsPerPage={rowsPerPage}
+										page={page}
+										onPageChange={handleChangePage}
+										SelectProps={{
+											inputProps: { 'aria-label': 'rows per page' },
+											native: true,
+										}}
+										onRowsPerPageChange={handleChangeRowsPerPage}
+										ActionsComponent={TablePaginationActions}
+									/>
+								</TableRow>
+							</TableFooter>
+						</Table>
+					</TableContainer>
+				</>
+      )}
+    </SubBlock>
+  );
+};
+
+export default WorkGroup;

+ 45 - 109
src/components/Dashboard/index.tsx

@@ -1,124 +1,60 @@
+import React, { useEffect, useState } from "react";
+import { IState } from "../../types";
 import { Container, Grid } from "@material-ui/core";
+import Memberships from "./Memberships";
+import Channels from "./Channels";
+import Videos from "./Videos";
+import Forum from "./Forum";
+import Election from "./Election";
+import Validation from "./Validation";
+import WorkGroup from './Workgroup';
 
-interface IProps extends IState {
-  blocks: { id: number; events: any }[];
-}
+import Banner from "../ui/Banner";
+import { useElectedCouncils } from '@/hooks';
+import { ElectedCouncil } from "@/graphtypes";
+import Proposals from "./Proposals";
 
-const data = {
-  memberships: { total: 8000, invited: 100, created: 99 },
-  channels: { created: 5, total: 10000 },
-  videos: { total: 3333, new: 23 },
-  nft: { issued: 10, volume: 9123809, platformFee: 23123 },
-  forum: { threads: { new: 13, total: 432 }, posts: { new: 99, total: 712 } },
-  election: { candidates: 5, staked: 99093123, votes: 232 },
-  validation: { count: 100, rewards: 1000, staked: 99999999 },
-  minted: { total: 3423432, cm: 3243, funded: 0 },
-  proposals: { created: 34, dormant: 6, executed: 8, rejected: 4 },
-};
 
-const Dashboard = (props: IProps) => {
-  //const { save, selectVideo, media, categories } = props;
-  const {
-    memberships,
-    channels,
-    videos,
-    nft,
-    forum,
-    election,
-    validation,
-    minted,
-    proposals,
-  } = data;
+const Dashboard = (props) => {
+  const { } = props;
+  const { data } = useElectedCouncils({});
+	const [description1, setDescription1] = useState('');
+
+	const council: ElectedCouncil | undefined = data && data[0]
+
+	useEffect(() => {
+		if (!council) 
+      return
+
+		setDescription1(
+			"Round: " + council.electionCycleId + 
+			", From: " + new Date(council.electedAt.timestamp) + 
+			", Councilors: [ " + council.councilors.map(each => each.member.handle).join(", ") + " ]")
+
+	}, [council])
+ 
+
   return (
     <div style={{ flexGrow: 1 }}>
       <Container maxWidth="xl">
         <Grid container spacing={3}>
-          <Box wide>
-            For a given council period (so there needs to be an input field for
-            this), I want to see a nice one page dashboard which shows the
-            following, and nothing else (each at end of period)
-          </Box>
-          <div className="d-flex flex-wrap">
-            <Box name="Memberships">
-              <Fact name="created" value={memberships.created} />
-              <Fact name="created" value={memberships.invited} />
-              <Fact name="total" value={memberships.total} />
-            </Box>
-            <Box name="Channels">
-              <Fact name="created" value={channels.created} />
-              <Fact name="total" value={channels.total} />
-            </Box>
-            <Box name="Videos">
-              <Fact name="created" value={videos.created} />
-              <Fact name="total" value={videos.total} />
-            </Box>
-            <Box name="NFT">
-              <Fact name="isued" value={nft.issued} />
-              <Fact name="sale volume" value={nft.volume} />
-              <Fact name="total platform fee" value={nft.platformFee} />
-            </Box>
-            <Box name="Forum">
-              <Fact name="threads new" value={forum.threads.new} />
-              <Fact name="threads total" value={forum.threads.total} />
-              <Fact name="posts new" value={forum.posts.new} />
-              <Fact name="posts total" value={forum.posts.total} />
-            </Box>
-            <Box name="Election">
-              <Fact name="candidates" value={election.candidates} />
-              <Fact name="votes" value={election.votes} />
-              <Fact name="staked" value={election.staked} />
-            </Box>
-            <Box name="Validation">
-              <Fact name="count" value={validation.count} />
-              <Fact name="minted" value={validation.rewards} />
-              <Fact name="staked" value={validation.staked} />
-            </Box>
-            <Box name="Minted">
-              <Fact name="total" value={minted.total} />
-              <Fact name="CM" value={minted.cm} />
-              <Fact name="funded" value={minted.funded} />
-            </Box>
-            <Box name="WG" wide>
-              new tokens minted size of budget at end of period amount of debt
-              at end of period number of workers at end of period
-            </Box>
-            <Box name="Proposals" wide>
-              <Fact name="created" value={proposals.created} />
-              <Fact name="executed" value={proposals.executed} />
-              <Fact name="dormant" value={proposals.dormant} />
-              <Fact name="rejected" value={proposals.rejected} />
-              Proposals: title, creation date, and link to full proposal on
-              Pioneer hosted on Joystream.org - created - failed - passed
-            </Box>
-          </div>
+          <Banner description={description1}/>
+        </Grid>
+        <Grid container spacing={3}>
+          <Memberships council={council}/>
+          <Channels council={council}/>
+          <Videos council={council}/>
+          <Forum council={council}/>
+          <Election council={council}/>  
+          <Validation council={council}/>
+        </Grid>
+        <Grid container spacing={3}>
+          <WorkGroup council={council}/>
+          <Proposals council={council} />
         </Grid>
       </Container>
     </div>
   );
 };
 
-const Box = (props: { name: string; children: any; wide?: boolean }) => {
-  const { name, children, wide } = props;
-  const width = wide ? "w-100" : "w-25";
-  const style = wide ? null : { minHeight: "20vh" };
-  return (
-    <div className={`p-1 10vt ${width}`}>
-      <div className="bg-primary m-1 p-3" style={style}>
-        <h2>{name}</h2>
-        {children}
-      </div>
-    </div>
-  );
-};
-
-const Fact = (props: { name: string; value: number }) => {
-  const { name, value } = props;
-  return (
-    <>
-      <div className="float-right">{value}</div>{" "}
-      <div className="font-weight-bold">{name}</div>
-    </>
-  );
-};
-
 export default Dashboard;

+ 69 - 0
src/components/ui/Banner.tsx

@@ -0,0 +1,69 @@
+import React from "react";
+import {
+  Paper,
+  Grid,
+  makeStyles,
+  Theme,
+  createStyles,
+  Toolbar,
+  Typography,
+  AppBar,
+} from "@material-ui/core";
+import Line from "./Line";
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    grid: { textAlign: "center", backgroundColor: "#000", color: "#fff" },
+    root: { flexGrow: 1, backgroundColor: "#4038FF", boxShadow: "none", paddingLeft:"16px"  },
+    title: { textAlign: "left", flexGrow: 1, color: '#000' },
+    toolbar: { minHeight:'40px' },
+    description: { textAlign: "left", flexGrow: 1, color: '#000', 
+        paddingLeft:"16px", paddingRight:"16px", paddingTop:"8px", paddingBottom:"8px"
+    },
+    paper: {
+      textAlign: "center",
+      backgroundColor: "#4038FF",
+      color: "#fff",
+      minHeight: 50,
+      maxHeight: 200,
+      overflow: "auto",
+      paddingTop:"6px",
+      paddingBottom:"6px"
+    },
+  })
+);
+
+const Banner = (props: {
+  title: string;
+  description: string;
+}) => {
+  const { 
+    title,
+    description
+  } = props;
+  const classes = useStyles();
+
+  return (
+    <Grid className={classes.grid} item xs={12}>
+      <Paper className={classes.paper}>
+        { title && 
+          <AppBar className={classes.root} position="static">
+            <Toolbar disableGutters={true} className={classes.toolbar}>
+              <Typography variant="h6" className={classes.title}>
+                {title}
+              </Typography>
+            </Toolbar>
+          </AppBar>
+        }
+        <i>
+          <Line content={description} />
+        </i>
+        {/* <Typography variant="body1" className={classes.description}>
+            {description}
+        </Typography> */}
+      </Paper>
+    </Grid>
+  );
+};
+
+export default Banner;

+ 23 - 0
src/components/ui/Line.tsx

@@ -0,0 +1,23 @@
+import React from "react";
+import {
+  Box
+} from "@material-ui/core";
+
+const Line = (props: {
+    content: String,
+    value: any
+}) => {
+  const { 
+    content,
+    value
+  } = props;
+
+  return (
+    <Box sx={{ display: 'flex', paddingLeft:'16px', paddingRight:'16px', color: '#000' }} >
+			<Box sx={{ flexGrow: 1, textAlign: "left" }}>{content}</Box>
+			<Box sx={{ textAlign: "right" }}><i>{value}</i></Box>
+    </Box>
+  );
+};
+
+export default Line;

+ 63 - 0
src/components/ui/SubBlock.tsx

@@ -0,0 +1,63 @@
+import React from "react";
+import {
+  Paper,
+  Grid,
+  makeStyles,
+  Theme,
+  createStyles,
+  Toolbar,
+  Typography,
+  AppBar,
+} from "@material-ui/core";
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    grid: { textAlign: "center", backgroundColor: "#000", color: "#fff" },
+    root: { flexGrow: 1, backgroundColor: "#4038FF", boxShadow: "none", paddingLeft:"16px" },
+    title: { textAlign: "left", flexGrow: 1, color: '#000' },
+    toolbar: { minHeight:'40px' },
+    paper: {
+      textAlign: "center",
+      backgroundColor: "#4038FF",
+      color: "#fff",
+      minHeight: 100,
+      // maxHeight: 400,
+      overflow: "auto",
+      paddingTop:"6px",
+      paddingBottom:"6px"
+    },
+  })
+);
+
+const SubBlock = (props: {
+  title: string,
+  stretch?: number,
+  children: any
+}) => { 
+  const { 
+    title,
+    stretch,
+    children,
+  } = props;
+
+  let classes = useStyles();
+
+  return (
+    <Grid className={classes.grid} item xs={stretch? stretch: 4} md={stretch? stretch: 4} sm={12}>
+      <Paper className={classes.paper}>
+        { title && 
+          <AppBar className={classes.root} position="static">
+            <Toolbar disableGutters={true} className={classes.toolbar}>
+              <Typography variant="h6" className={classes.title}>
+                {title}
+              </Typography>
+            </Toolbar>
+          </AppBar>
+        }
+        {children}
+      </Paper>
+    </Grid>
+  );
+};
+
+export default SubBlock;

+ 76 - 0
src/components/ui/TablePaginationActions.tsx

@@ -0,0 +1,76 @@
+import { makeStyles, useTheme, Theme, createStyles } from '@material-ui/core/styles';
+
+import IconButton from '@material-ui/core/IconButton';
+import FirstPageIcon from '@material-ui/icons/FirstPage';
+import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft';
+import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight';
+import LastPageIcon from '@material-ui/icons/LastPage';
+
+const useStyles1 = makeStyles((theme: Theme) =>
+  createStyles({
+    root: {
+      flexShrink: 0,
+      marginLeft: theme.spacing(2.5),
+    },
+  }),
+);
+
+interface TablePaginationActionsProps {
+  count: number;
+  page: number;
+  rowsPerPage: number;
+  onPageChange: (event: React.MouseEvent<HTMLButtonElement>, newPage: number) => void;
+}
+
+const TablePaginationActions = (props: TablePaginationActionsProps) => {
+	const classes = useStyles1();
+  const theme = useTheme();
+  const { count, page, rowsPerPage, onPageChange } = props;
+
+  const handleFirstPageButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
+    onPageChange(event, 0);
+  };
+
+  const handleBackButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
+    onPageChange(event, page - 1);
+  };
+
+  const handleNextButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
+    onPageChange(event, page + 1);
+  };
+
+  const handleLastPageButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
+    onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
+  };
+
+  return (
+    <div className={classes.root}>
+      <IconButton
+        onClick={handleFirstPageButtonClick}
+        disabled={page === 0}
+        aria-label="first page"
+      >
+        {theme.direction === 'rtl' ? <LastPageIcon /> : <FirstPageIcon />}
+      </IconButton>
+      <IconButton onClick={handleBackButtonClick} disabled={page === 0} aria-label="previous page">
+        {theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
+      </IconButton>
+      <IconButton
+        onClick={handleNextButtonClick}
+        disabled={page >= Math.ceil(count / rowsPerPage) - 1}
+        aria-label="next page"
+      >
+        {theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
+      </IconButton>
+      <IconButton
+        onClick={handleLastPageButtonClick}
+        disabled={page >= Math.ceil(count / rowsPerPage) - 1}
+        aria-label="last page"
+      >
+        {theme.direction === 'rtl' ? <FirstPageIcon /> : <LastPageIcon />}
+      </IconButton>
+    </div>
+  );
+}
+
+export default TablePaginationActions;

+ 2 - 1
src/config.ts

@@ -7,7 +7,8 @@ export const wsLocation = "wss://rpc.joystream.org";
 //export const wsLocation = "wss://pl.joystreamstats.live/rpc";
 
 export const hydraLocation = "https://joystreamstats.live/graphql"
-export const queryNode= "https://joystreamstats.live/graphql"
+export const queryNode= "https://tiguan08.com/graphql"
+// export const queryNode= "https://joystreamstats.live/graphql"
 
 export const apiLocation = "https://joystreamstats.live/api"
 export const socketLocation = "/socket.io"

+ 0 - 0
src/types/Councilor.ts → src/graphtypes/Councilor.ts


+ 0 - 0
src/types/ElectedCouncil.ts → src/graphtypes/ElectedCouncil.ts


+ 0 - 0
src/types/Member.ts → src/graphtypes/Member.ts


+ 0 - 0
src/types/Proposal.ts → src/graphtypes/Proposal.ts


+ 0 - 0
src/types/Worker.ts → src/graphtypes/Worker.ts


+ 0 - 0
src/types/WorkingGroup.ts → src/graphtypes/WorkingGroup.ts


+ 0 - 0
src/types/common/Block.ts → src/graphtypes/common/Block.ts


+ 0 - 0
src/types/common/Dates.ts → src/graphtypes/common/Dates.ts


+ 0 - 0
src/types/common/casting.ts → src/graphtypes/common/casting.ts


+ 0 - 0
src/types/common/form.ts → src/graphtypes/common/form.ts


+ 0 - 0
src/types/common/helpers.ts → src/graphtypes/common/helpers.ts


+ 0 - 0
src/types/common/index.ts → src/graphtypes/common/index.ts


+ 0 - 0
src/types/common/utils.ts → src/graphtypes/common/utils.ts


+ 0 - 0
src/types/index.ts → src/graphtypes/index.ts


+ 15 - 0
src/helpers/bn.ts

@@ -0,0 +1,15 @@
+import { BN_TEN, BN_TWO, BN_ZERO } from '@polkadot/util';
+import BN from 'bn.js';
+
+type BNParam = number | string | number[] | Uint8Array | Buffer | BN;
+
+export const sumStakes = (entities: { stake: BNParam }[]) =>
+  entities.reduce((total, { stake }) => total.add(new BN(stake)), BN_ZERO);
+
+export const asBN = (value: any) => new BN(String(value));
+
+export const sumBN = (a: BN | undefined, b: BN | undefined): BN => new BN(a ?? 0).add(new BN(b ?? 0));
+
+export const powerOf10 = (value: any) => BN_TEN.pow(asBN(value));
+
+export const powerOf2 = (value: any) => BN_TWO.pow(asBN(value));

+ 2 - 0
src/helpers/index.ts

@@ -0,0 +1,2 @@
+export * from './bn';
+export * from './utils';

+ 94 - 0
src/helpers/utils.ts

@@ -0,0 +1,94 @@
+export const capitalizeFirstLetter = <T extends string>(str: T) =>
+  (str.charAt(0).toUpperCase() + str.slice(1)) as Capitalize<T>;
+
+export const lowerFirstLetter = (str: string): string => str.charAt(0).toLowerCase() + str.slice(1);
+
+export const plural = (quantity?: unknown, suffix = 's') => (quantity === 1 ? '' : suffix);
+
+export const cutText = (text: string, length = 100) => (text.length > length ? text.slice(0, length) + '...' : text);
+
+export const isInFuture = (time: string) => {
+  const timeAsDate = new Date(time).valueOf();
+  return timeAsDate > Date.now();
+};
+
+export const nameMapping = (value: string) => {
+  switch (value) {
+    case 'Operations Alpha':
+      return 'Builders';
+    case 'App':
+      return 'Apps';
+    case 'Operations Beta':
+      return 'HR';
+    case 'Operations Gamma':
+      return 'Marketing';
+    default:
+      return value;
+  }
+};
+
+export const wgListItemMappings = (value: string) => {
+  switch (value) {
+    case 'Operations Alpha':
+      return {
+        subtitle:
+          'A diverse set of contributors, such as Developers, Designers and Product Managers, responsible for development of infrastructure and user facing applications.',
+        tooltipLink: 'https://joystream.gitbook.io/testnet-workspace/system/builders',
+      };
+    case 'Storage':
+      return {
+        subtitle:
+          'Broadly responsible for ensuring storage infrastructure uptime, namely running complete and up-to-date copy of the content directory and accept inbound uploads from end users.',
+        tooltipLink: 'https://joystream.gitbook.io/testnet-workspace/system/storage',
+      };
+    case 'Content':
+      return {
+        subtitle:
+          'Monitor publishing of the new content into the content directory, respond to the reported publications and adjudicate possible dispute processes.',
+        tooltipLink: 'https://joystream.gitbook.io/testnet-workspace/system/content-directory',
+      };
+    case 'Distribution':
+      return {
+        subtitle:
+          'Run and maintain distributor nodes that deliver large volumes of upstream data to a large number of simultaneous end users.',
+        tooltipLink: 'https://joystream.gitbook.io/testnet-workspace/system/storage#distributor',
+      };
+    case 'App':
+      return {
+        subtitle:
+          'Apps group runs multiple video streaming apps working on Joystream blockchain and provides support to all external app operators.',
+        tooltipLink: 'https://joystream.gitbook.io/testnet-workspace/system/gateways',
+      };
+    case 'Operations Beta':
+      return {
+        subtitle:
+          'Human Resources working group is responsible for integrating new members greeting, onboarding, catalyzing and nurturing, as well as managing bounties.',
+        tooltipLink:
+          'https://joystream.gitbook.io/testnet-workspace/testnet/council-period-scoring/human-resources-score',
+      };
+    case 'Operations Gamma':
+      return {
+        subtitle:
+          'Marketing group is responsible for increasing the outreach, sharing the content from the platform with the world, spreading the news about platform development, new members acquisition and overall growth.',
+        tooltipLink: 'https://joystream.gitbook.io/testnet-workspace/system/marketers',
+      };
+    case 'Membership':
+      return {
+        subtitle:
+          'Membership group is responsible for new memberships invitations, referral rewards for existing members and overall process of adding more members via referral scheme.',
+        tooltipLink:
+          'https://joystream.gitbook.io/testnet-workspace/testnet/council-period-scoring/human-resources-score',
+      };
+    case 'Forum':
+      return {
+        subtitle:
+          'Monitor and supervise public communication channels for compliance with usage policies as decided through the governance system.',
+        tooltipLink: 'https://joystream.gitbook.io/testnet-workspace/system/forum',
+      };
+    default:
+      return {
+        subtitle: value,
+        tooltipLink: undefined,
+      };
+  }
+};

+ 1 - 1
src/hooks/types.ts

@@ -1,4 +1,4 @@
-import { ElectedCouncil } from '@/types';
+import { ElectedCouncil } from '@/graphtypes';
 
 export interface ForSelectedCouncil {
   council?: ElectedCouncil;

+ 1 - 1
src/hooks/useCouncilMembers.ts

@@ -3,7 +3,7 @@ import { useEffect, useMemo } from 'react';
 import { useGetCouncilMembersLazyQuery } from '@/queries';
 
 import { ForSelectedCouncil } from './types';
-import { asCouncilMember } from '@/types';
+import { asCouncilMember } from '@/graphtypes';
 
 export function useCouncilMembers({ council }: ForSelectedCouncil) {
 

+ 1 - 1
src/hooks/useElectedCouncils.ts

@@ -1,5 +1,5 @@
 import { ElectedCouncilOrderByInput, GetElectedCouncilsQueryVariables, useGetElectedCouncilsQuery } from '@/queries';
-import { asElectedCouncil } from '@/types';
+import { asElectedCouncil } from '@/graphtypes';
 
 export const useElectedCouncils = ({
   orderBy = ElectedCouncilOrderByInput.CreatedAtDesc,

+ 1 - 1
src/hooks/useGroupWorkers.ts

@@ -1,7 +1,7 @@
 import { useEffect, useMemo } from 'react';
 
 import { useGetWorkersLazyQuery } from '@/queries';
-import { asWorker, WorkingGroup } from '@/types';
+import { asWorker, WorkingGroup } from '@/graphtypes';
 
 import { ForSelectedCouncil } from './types';
 

+ 1 - 1
src/hooks/useProposals.ts

@@ -1,7 +1,7 @@
 import { useEffect, useMemo } from 'react';
 
 import { useGetProposalsLazyQuery } from '@/queries';
-import { asProposal } from '@/types';
+import { asProposal } from '@/graphtypes';
 
 import { ForSelectedCouncil } from './types';
 

+ 1 - 1
src/hooks/useWorker.ts

@@ -1,7 +1,7 @@
 import { useEffect, useMemo } from 'react';
 
 import { useGetTerminatedWorkderLazyQuery, useGetWorkerExitedLazyQuery, useGetOpeningFilledLazyQuery } from '@/queries';
-import { asWorkingGroup } from '@/types';
+import { asWorkingGroup } from '@/graphtypes';
 
 import { ForSelectedCouncil } from './types';
 

+ 1 - 1
src/hooks/useWorkingGroups.ts

@@ -1,7 +1,7 @@
 import { useEffect, useMemo } from 'react';
 
 import { useGetWorkingGroupsLazyQuery, useWorkingGroupTokenLazyQuery, useGetRewardsLazyQuery } from '@/queries';
-import { asWorkingGroup } from '@/types';
+import { asWorkingGroup } from '@/graphtypes';
 
 import { ForSelectedCouncil } from './types';
 

+ 4 - 1
src/index.tsx

@@ -1,6 +1,7 @@
 import React from "react";
 import ReactDOM from "react-dom";
 import { BrowserRouter as Router } from "react-router-dom";
+import Providers from "./Providers";
 import "./index.css";
 import App from "./App";
 
@@ -8,7 +9,9 @@ import "./i18n";
 
 ReactDOM.render(
   <Router>
-    <App />
+    <Providers>
+      <App />
+    </Providers>
   </Router>,
   document.getElementById("root")
 );

+ 0 - 0
src/queries.ts → src/queries_.ts