Joystream Stats 2 éve
szülő
commit
5daf8dfceb

+ 11 - 1
src/App.tsx

@@ -41,6 +41,7 @@ const initialState = {
   stars: {},
   hideFooter: true,
   showStatus: false,
+  editKpi: false,
   status: { era: 0, block: { id: 0, era: 0, timestamp: 0, duration: 6 } },
 };
 
@@ -498,6 +499,9 @@ class App extends React.Component<IProps, IState> {
     }
   }
 
+  toggleEditKpi(editKpi) {
+    this.setState({ editKpi });
+  }
   toggleShowStatus() {
     this.setState({ showStatus: !this.state.showStatus });
   }
@@ -512,6 +516,7 @@ class App extends React.Component<IProps, IState> {
     return (
       <>
         <Routes
+          toggleEditKpi={this.toggleEditKpi}
           toggleFooter={this.toggleFooter}
           toggleStar={this.toggleStar}
           getMember={this.getMember}
@@ -520,7 +525,11 @@ class App extends React.Component<IProps, IState> {
           {...this.state}
         />
 
-        <Modals toggleShowStatus={this.toggleShowStatus} {...this.state} />
+        <Modals
+          toggleEditKpi={this.toggleEditKpi}
+          toggleShowStatus={this.toggleShowStatus}
+          {...this.state}
+        />
 
         <Footer show={!hideFooter} toggleHide={this.toggleFooter} />
 
@@ -568,6 +577,7 @@ class App extends React.Component<IProps, IState> {
     this.state = initialState;
     this.fetchTokenomics = this.fetchTokenomics.bind(this);
     this.load = this.load.bind(this);
+    this.toggleEditKpi = this.toggleEditKpi.bind(this);
     this.toggleStar = this.toggleStar.bind(this);
     this.toggleFooter = this.toggleFooter.bind(this);
     this.toggleShowStatus = this.toggleShowStatus.bind(this);

+ 23 - 109
src/components/KPI/Round/KpiBox.tsx

@@ -1,115 +1,29 @@
 import { useState } from "react";
-import { Button } from "react-bootstrap";
-import { Edit } from "react-feather";
-
-import Markdown from "react-markdown";
-import gfm from "remark-gfm";
-
-const KPI = (props: { kpi: any; id: string; edit: string }) => {
-  const { kpi, edit, id, start, end, toggleEdit } = props;
-  const [showDetails, toggleShowDetails] = useState(false);
-  const {
-    title,
-    reward,
-    fiatPool,
-    distribution,
-    process,
-    active,
-    purpose,
-    scope,
-    rewardDistribution,
-  } = kpi;
-
-  if (edit === id) return <div id={id}>Form here</div>;
 
+import Details from "./KpiDetails";
+import Stats from "./KpiStats";
+
+const KPI = (props: {
+  kpi: any;
+  id: string;
+  edit: string;
+  toggleEdit: (any) => void;
+}) => {
+  const { id, start, end, toggleEdit } = props;
+  const [showDetails, toggleDetails] = useState(false);
+  const kpi = { id, ...props.kpi };
   return (
-    <div
-      id={id}
-      className="m-2 col-12 col-md-5 col-lg-3"
-      onClick={() => toggleShowDetails(!showDetails)}
-    >
-      <Button variant="secondary" className="p-0 btn-sm float-right">
-        <Edit onClick={() => toggleEdit(id)} />
-      </Button>
-
-      <b>{title}</b>
-
-      <ul>
-        <li>
-          <b>Reward:</b> {reward}
-        </li>
-        <li>
-          <b>Fiat Pool Factor:</b> {fiatPool}
-        </li>
-        <li>
-          <b>Reward Distribution:</b> {distribution}
-        </li>
-        <li>
-          <b>Grading Process:</b> {process}
-        </li>
-        <li>
-          <b>Active:</b> {active}
-          <ul>
-            <li>
-              <b>Start Block:</b> {start}
-            </li>
-            <li>
-              <b>End Block:</b> {end}
-            </li>
-          </ul>
-        </li>
-      </ul>
-
-      {showDetails ? (
-        <>
-          {purpose ? (
-            <div>
-              <h6>Purpose</h6>
-              <Markdown
-                plugins={[gfm]}
-                className="overflow-auto text-left"
-                children={purpose}
-              />
-            </div>
-          ) : (
-            ""
-          )}
-          {scope ? (
-            <div>
-              <h6>Scope of Work</h6>
-              {scope.length ? (
-                scope.map((text) => (
-                  <Markdown
-                    plugins={[gfm]}
-                    className="overflow-auto text-left"
-                    children={text}
-                  />
-                ))
-              ) : (
-                <Markdown
-                  plugins={[gfm]}
-                  className="overflow-auto text-left"
-                  children={scope}
-                />
-              )}
-            </div>
-          ) : (
-            ""
-          )}
-          {rewardDistribution ? (
-            <Markdown
-              plugins={[gfm]}
-              className="overflow-auto text-left"
-              children={rewardDistribution}
-            />
-          ) : (
-            ""
-          )}
-        </>
-      ) : (
-        ""
-      )}
-    </div>
+    <>
+      <Stats
+        kpi={kpi}
+        start={start}
+        end={end}
+        showDetails={showDetails}
+        toggleDetails={toggleDetails}
+        toggleEdit={toggleEdit}
+      />
+      <Details kpi={kpi} show={showDetails} toggleEdit={toggleEdit} />
+    </>
   );
 };
 

+ 70 - 0
src/components/KPI/Round/KpiDetails.tsx

@@ -0,0 +1,70 @@
+import { Button } from "react-bootstrap";
+import { Edit } from "react-feather";
+
+import Markdown from "react-markdown";
+import gfm from "remark-gfm";
+
+const Details = (props) => {
+  const { show, kpi, toggleEdit } = props;
+  const { id, title, purpose, scope, rewardDistribution } = kpi;
+
+  if (!show) return <div />;
+  return (
+    <div className="w-100 bg-dark p-3">
+      <Button variant="" className="p-0 btn-sm float-right">
+        <Edit color="white" onClick={() => toggleEdit(kpi)} />
+      </Button>
+
+      <h2>
+        {id} {title}
+      </h2>
+
+      {purpose ? (
+        <div>
+          <h6>Purpose</h6>
+          <Markdown
+            plugins={[gfm]}
+            className="overflow-auto text-left"
+            children={purpose}
+          />
+        </div>
+      ) : (
+        ""
+      )}
+      {scope ? (
+        <div>
+          <h6>Scope of Work</h6>
+          {scope.length ? (
+            scope.map((text, i) => (
+              <Markdown
+                key={i}
+                plugins={[gfm]}
+                className="overflow-auto text-left"
+                children={text}
+              />
+            ))
+          ) : (
+            <Markdown
+              plugins={[gfm]}
+              className="overflow-auto text-left"
+              children={scope}
+            />
+          )}
+        </div>
+      ) : (
+        ""
+      )}
+      {rewardDistribution ? (
+        <Markdown
+          plugins={[gfm]}
+          className="overflow-auto text-left"
+          children={rewardDistribution}
+        />
+      ) : (
+        ""
+      )}
+    </div>
+  );
+};
+
+export default Details;

+ 49 - 0
src/components/KPI/Round/KpiStats.tsx

@@ -0,0 +1,49 @@
+//import ToggleDetails from "./ToggleDetails"
+
+const KpiStats = (props) => {
+  const { kpi, showDetails, toggleDetails, start, end } = props;
+  const { id, title, reward, fiatPool, distribution, process, active } = kpi;
+
+  return (
+    <div
+      id={id}
+      className={`py-3 col-12 col-md-6 ${showDetails ? "bg-dark" : ""}`}
+      title={showDetails ? `click to expand` : `click to fold`}
+      onClick={() => toggleDetails(!showDetails)}
+    >
+      <b>{title}</b>
+
+      <ul>
+        <li>
+          <b>ID:</b> {id}
+        </li>
+        <li>
+          <b>Reward:</b> {reward}
+        </li>
+        <li>
+          <b>Fiat Pool Factor:</b> {fiatPool}
+        </li>
+        <li>
+          <b>Reward Distribution:</b> {distribution}
+        </li>
+        <li>
+          <b>Grading Process:</b> {process}
+        </li>
+        <li>
+          <b>Active:</b> {active}
+          <ul>
+            <li>
+              <b>Start Block:</b> {start}
+            </li>
+            <li>
+              <b>End Block:</b> {end}
+            </li>
+          </ul>
+        </li>
+      </ul>
+    </div>
+  );
+};
+
+export default KpiStats;
+

+ 19 - 28
src/components/KPI/Round/TOC.tsx

@@ -1,36 +1,27 @@
 const TOC = (props) => {
-  const { focus } = props;
+  const { section, focus } = props;
   const { kpis, round } = props.round;
 
   return (
-    <div className="my-2">
+    <div className="mt-4">
       <ul>
-        <li>
-          KPI {round}
-          <ul>
-            {kpis.sections.map(({ name, kpis }, section: number) => (
-              <li>
-                <div
-                  onClick={() => focus(`#${round}-${section + 1}`)}
-                  href={`#${round}-${section + 1}`}
-                >
-                  {name}
-                </div>
-                <ul>
-                  {kpis.map(({ title }, kpi: number) => (
-                    <li
-                      onClick={() =>
-                        focus(`#${round}-${section + 1}-${kpi + 1}`)
-                      }
-                    >
-                      {title}
-                    </li>
-                  ))}
-                </ul>
-              </li>
-            ))}
-          </ul>
-        </li>
+        {kpis.sections.map(({ id, name, kpis }) => (
+          <li key={id}>
+            <div
+              onClick={() => focus(`${round}-${id}`)}
+              href={`#${round}-${section}`}
+            >
+              {name}
+            </div>
+            <ul>
+              {kpis.map(({ title }, i: number) => (
+                <li key={i} onClick={() => focus(`${round}-${id}-${i + 1}`)}>
+                  {round}.{id}-{i + 1} {title}
+                </li>
+              ))}
+            </ul>
+          </li>
+        ))}
       </ul>
     </div>
   );

+ 22 - 0
src/components/KPI/Round/ToggleDetails.tsx

@@ -0,0 +1,22 @@
+import { Button } from "react-bootstrap";
+import { PlusSquare, MinusSquare } from "react-feather";
+
+const ToggleDetails = (props) => {
+  const { toggleDetails, showDetails } = props;
+  return (
+    <Button
+      variant=""
+      className="p-0 btn-sm float-left"
+      onClick={() => toggleDetails(!showDetails)}
+    >
+      {showDetails ? (
+        <MinusSquare color="white" />
+      ) : (
+        <PlusSquare color="white" />
+      )}
+    </Button>
+  );
+};
+
+export default ToggleDetails;
+

+ 35 - 30
src/components/KPI/Round/index.tsx

@@ -3,47 +3,52 @@ import { RefreshCw } from "react-feather";
 import Markdown from "react-markdown";
 import gfm from "remark-gfm";
 
-import TOC from "../TOC";
+import TOC from "./TOC";
 import Stats from "./Stats";
-import KPI from "../KpiBox";
+import KPI from "./KpiBox";
 
 const Round = (props) => {
-  const { edit, toggleEditKpi, focus, fetchKpi } = props;
+  const { toggleEditKpi, focus, fetchKpi } = props;
   const { round, notes, kpis, startBlock, endBlock } = props.round;
+
   return (
     <div>
-      <Button variant="secondary" className="p-1 btn-sm float-right">
+      <Button variant="" className="p-1 btn-sm float-right">
         <RefreshCw onClick={fetchKpi} />
       </Button>
 
-      <TOC focus={focus} round={props.round} />
-      <h3>Round {round}</h3>
-      <Stats round={round} />
-
-      <Markdown
-        plugins={[gfm]}
-        className="overflow-auto text-left"
-        children={notes}
-      />
+      <div className="d-flex flex-wrap">
+        <div className="col-12 col-lg-5">
+          <TOC focus={focus} round={props.round} />
+          <h3>Round {round}</h3>
+          <Stats round={props.round} />
 
-      {kpis.sections.map((section, index: number) => (
-        <div key={index} id={`#${round}-${index}`}>
-          <h4>{section.name}</h4>
-          <div className="d-flex flex-wrap">
-            {section.kpis.map((kpi, i: number) => (
-              <KPI
-                key={i}
-                toggleEdit={toggleEditKpi}
-                id={`#${round}-${index + 1}-${i + 1}`}
-                kpi={kpi}
-                edit={edit}
-                start={startBlock}
-                end={endBlock}
-              />
-            ))}
-          </div>
+          <Markdown
+            plugins={[gfm]}
+            className="overflow-auto text-left"
+            children={notes}
+          />
+        </div>
+        <div className="col-12 col-lg-7">
+          {kpis.sections.map((section, index: number) => (
+            <div key={index} id={`${round}-${section.id}`}>
+              <h4>{section.name}</h4>
+              <div className="d-flex flex-wrap">
+                {section.kpis.map((kpi, i: number) => (
+                  <KPI
+                    key={i}
+                    id={`${round}-${section.id}-${i + 1}`}
+                    toggleEdit={toggleEditKpi}
+                    kpi={kpi}
+                    start={startBlock}
+                    end={endBlock}
+                  />
+                ))}
+              </div>
+            </div>
+          ))}
         </div>
-      ))}
+      </div>
     </div>
   );
 };

+ 54 - 36
src/components/KPI/index.tsx

@@ -1,4 +1,5 @@
 import { Component } from "react";
+import { Link } from "react-router-dom";
 import { Button } from "react-bootstrap";
 import { Typography } from "@material-ui/core";
 import { Loading } from "..";
@@ -12,10 +13,9 @@ const baseUrl = `https://joystreamstats.live/static`;
 class KPI extends Component {
   constructor(props: { tokenomics: Tokenomics }) {
     super(props);
-    this.state = { rounds: [], leaderboard: [] };
+    this.state = { round: null, rounds: [], leaderboard: [] };
     this.fetchKpi = this.fetchKpi.bind(this);
     this.fetchLeaderboard = this.fetchLeaderboard.bind(this);
-    this.toggleEditKpi = this.toggleEditKpi.bind(this);
     this.toggleShowLeaderboard = this.toggleShowLeaderboard.bind(this);
   }
   componentDidMount() {
@@ -25,7 +25,15 @@ class KPI extends Component {
   fetchKpi() {
     axios
       .get(`${baseUrl}/kpi.json`)
-      .then(({ data }) => data.rounds && this.setState({ rounds: data.rounds }))
+      .then(({ data }) => {
+        const rounds = data.rounds;
+        console.debug(`Received KPI for ${rounds.length} round(s).`);
+        const round = rounds.reduce(
+          (r, max) => (r.round > max.round ? r : max),
+          rounds[0]
+        );
+        this.setState({ rounds, round });
+      })
       .catch((e) => console.error(`Failed to fetch KPI data.`, e));
   }
   fetchLeaderboard() {
@@ -35,32 +43,56 @@ class KPI extends Component {
       .catch((e) => console.error(`Failed to fetch Leadboard data.`, e));
   }
 
+  selectRound(selectedRound: number) {
+    this.setState({ selectedRound, showLeaderboard: false });
+  }
   toggleShowLeaderboard() {
     this.setState({ showLeaderboard: !this.state.showLeaderboard });
   }
-  toggleEditKpi(edit) {
-    this.setState({ edit });
-  }
   focus(id) {
     document.getElementById(id)?.scrollIntoView();
   }
 
   render() {
-    const { edit, rounds, leaderboard, showLeaderboard } = this.state;
-    if (!rounds.length) return <Loading target="KPI" />;
+    const { round, rounds, leaderboard, showLeaderboard } = this.state;
+    if (!round) return <Loading target="KPI" />;
     return (
       <div className="m-3 p-2 text-light">
-        <Typography variant="h2" className="mb-3">
+        <div className="d-flex flex-row">
+          <Button
+            variant={showLeaderboard ? "light" : "outline-secondary"}
+            className="p-1 btn-sm"
+            onClick={this.toggleShowLeaderboard}
+          >
+            Leaderboard
+          </Button>
+          <Link
+            to={`/bounties`}
+            className="mx-2 p-1 btn btn-sm btn-outline-secondary"
+          >
+            Bounties
+          </Link>
+          <Link to={`/issues`} className="p-1 btn btn-sm btn-outline-secondary">
+            Operations Tasks
+          </Link>
+        </div>
+
+        <Typography variant="h2" className="mt-3">
           KPI
         </Typography>
 
-        <Button
-          variant="outline-secondary"
-          className="p-1 btn-sm"
-          onClick={this.toggleShowLeaderboard}
-        >
-          Leaderboard
-        </Button>
+        <div className="d-flex flex-wrap">
+          {rounds.map((r) => (
+            <Button
+              key={r.round}
+              variant={r.round === round.round ? "light" : "outline-light"}
+              className="ml-1 p-1 flex-grow-0"
+              onClick={() => this.selectRound(round)}
+            >
+              {r.round}
+            </Button>
+          ))}
+        </div>
 
         {showLeaderboard ? (
           <Leaderboard
@@ -68,26 +100,12 @@ class KPI extends Component {
             tokenomics={this.props.tokenomics}
           />
         ) : (
-          <div className="d-flex flex-row py-2">
-            <div className="justify-content-start mr-3">
-              {rounds.map(({ round }) => (
-                <Button
-                  variant="outline-secondary"
-                  className="mb-1 p-1 flex-grow-0"
-                >
-                  {round}
-                </Button>
-              ))}
-            </div>
-
-            <Round
-              round={rounds[0]}
-              edit={edit}
-              fetchKpi={this.fetchKpi}
-              toggleEdit={this.toggleEditKpi}
-              focus={this.focus}
-            />
-          </div>
+          <Round
+            round={round}
+            fetchKpi={this.fetchKpi}
+            focus={this.focus}
+            toggleEditKpi={this.props.toggleEditKpi}
+          />
         )}
       </div>
     );

+ 142 - 0
src/components/Modals/EditKpi.tsx

@@ -0,0 +1,142 @@
+import { useState } from "react";
+import { textarea, Form, FormGroup } from "react-bootstrap";
+import { Button, Modal } from "react-bootstrap";
+//import moment from "moment";
+
+const EditKpi = (props) => {
+  const [kpi, handleChange] = useState(props.kpi);
+  const [tab, setTab] = useState();
+  const { save, cancel } = props;
+  const {
+    id,
+    title,
+    reward,
+    fiatPool,
+    distribution,
+    process,
+    active,
+    purpose,
+    scope,
+    rewardDistribution,
+    note,
+  } = kpi;
+
+  return (
+    <Modal
+      size="lg"
+      aria-labelledby="contained-modal-title-vcenter"
+      centered
+      show={true}
+      onHide={() => cancel()}
+    >
+      <Modal.Header closeButton>
+        <Modal.Title>Edit KPI {id}</Modal.Title>
+      </Modal.Header>
+      <Modal.Body className="">
+        {tab === "purpose" ? (
+          <Text handleChange={handleChange} value={purpose} name="purpose" />
+        ) : tab === "scope" ? (
+          <Text handleChange={handleChange} value={scope} name="scope" />
+        ) : tab === "distribution" ? (
+          <Text
+            handleChange={handleChange}
+            value={rewardDistribution}
+            name="rewardDistribution"
+          />
+        ) : tab === "note" ? (
+          <Text handleChange={handleChange} value={note} name="note" />
+        ) : (
+          <Form>
+            <Input handleChange={handleChange} name="title" value={title} />
+            <Input handleChange={handleChange} name="reward" value={reward} />
+            <Input
+              handleChange={handleChange}
+              name="fiatPool"
+              value={fiatPool}
+            />
+            <Input
+              handleChange={handleChange}
+              name="distribution"
+              value={distribution}
+            />
+            <Input handleChange={handleChange} name="process" value={process} />
+            <Input handleChange={handleChange} name="active" value={active} />
+          </Form>
+        )}
+        <div id="tabs" className="d-flex flex-row">
+          <Button
+            variant={tab === "" ? "dark" : "outline-dark"}
+            className="btn-sm mr-1"
+            onClick={() => setTab("")}
+          >
+            Overview
+          </Button>
+          <Button
+            variant={tab === "purpose" ? "dark" : "outline-dark"}
+            className="btn-sm mr-1"
+            onClick={() => setTab("purpose")}
+          >
+            Purpose
+          </Button>
+          <Button
+            variant={tab === "scope" ? "dark" : "outline-dark"}
+            className="btn-sm mr-1"
+            onClick={() => setTab("scope")}
+          >
+            Purpose
+          </Button>
+          <Button
+            variant={tab === "distribution" ? "dark" : "outline-dark"}
+            className="btn-sm mr-1"
+            onClick={() => setTab("distribution")}
+          >
+            Reward Distribution
+          </Button>
+          <Button
+            variant={tab === "note" ? "dark" : "outline-dark"}
+            className="btn-sm mr-1"
+            onClick={() => setTab("note")}
+          >
+            Note
+          </Button>
+        </div>
+      </Modal.Body>
+      <Modal.Footer className="d-flex flex-row">
+        <Button variant="outline" onClick={() => cancel()}>
+          Close
+        </Button>
+        <Button disabled={true} variant="dark" onClick={() => save(kpi)}>
+          Save
+        </Button>
+      </Modal.Footer>
+    </Modal>
+  );
+};
+
+export default EditKpi;
+
+const Text = (props: { name; value }) => (
+  <textarea
+    className="form-control mb-4"
+    name={props.name}
+    value={props.value}
+    placeholder={props.name}
+    onChange={props.handleChange}
+    rows="20"
+  >
+    {props.value}
+  </textarea>
+);
+
+const Input = (props: { name; value }) => (
+  <FormGroup>
+    <label>{props.name[0].toUpperCase() + props.name.slice(1)}</label>
+    <input
+      className="form-control"
+      name={props.name}
+      value={props.value}
+      placeholder={props.name}
+      onChange={props.handleChange}
+    />
+  </FormGroup>
+);

+ 6 - 3
src/components/Modals/index.tsx

@@ -1,11 +1,14 @@
-import React from "react";
 import Status from "./Status";
+import EditKpi from "./EditKpi";
 
 const Modals = (props) => {
-  const { showModal, toggleShowStatus, showStatus } = props;
+  const { editKpi, toggleEditKpi, showModal, toggleShowStatus, showStatus } =
+    props;
   return (
     <div>
-      {showStatus ? (
+      {editKpi ? (
+        <EditKpi kpi={editKpi} cancel={toggleEditKpi} />
+      ) : showStatus ? (
         <Status show={showStatus} onHide={toggleShowStatus} {...props} />
       ) : showModal === "validator" ? (
         <div />

+ 12 - 4
src/components/Routes/index.tsx

@@ -34,7 +34,7 @@ interface IProps extends IState {
 }
 
 const Routes = (props: IProps) => {
-  const { faq, reports, tokenomics } = props;
+  const { faq, reports, tokenomics, toggleEditKpi } = props;
 
   return (
     <div>
@@ -133,9 +133,17 @@ const Routes = (props: IProps) => {
                 render={(routeprops) => <Burners {...routeprops} {...props} />}
               />
               <Route path="/faq" render={(routeprops) => <FAQ faq={faq} />} />
-              <Route path="/election" render={(routeprops) => <Election {...props}/>} />
-              <Route path="/kpi" render={(routeprops) => <KPI faq={faq} />} />
-              <Route path="/issues" render={(routeprops) => <IssueTracker/>} />
+              <Route
+                path="/election"
+                render={(routeprops) => <Election {...props} />}
+              />
+              <Route
+                path="/kpi"
+                render={(routeprops) => (
+                  <KPI toggleEditKpi={toggleEditKpi} faq={faq} />
+                )}
+              />
+              <Route path="/issues" render={(routeprops) => <IssueTracker />} />
               <Route path="/survey" render={(routeprops) => <Survey />} />
 
               <Route path="/" render={() => <Dashboard {...props} />} />

+ 1 - 0
src/types.ts

@@ -137,6 +137,7 @@ export interface IState {
   rewardPoints?: RewardPoints;
   hideFooter: boolean;
   showStatus: boolean;
+  editKpi: any; // TODO
   getMember: (m: string | number) => Member;
 }