aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.tsx25
-rw-r--r--src/Router.tsx30
-rw-r--r--src/Search.tsx54
-rw-r--r--src/Table.tsx124
-rw-r--r--src/data.ts16
-rw-r--r--src/index.css91
-rw-r--r--src/main.tsx10
-rw-r--r--src/vite-env.d.ts1
8 files changed, 351 insertions, 0 deletions
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..8845354
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,25 @@
+import { useEffect, useState } from "react";
+import Router from "./Router";
+import { setNobelPrizes } from "./data";
+
+let loadOnce = false;
+function App() {
+ const [loading, setLoading] = useState(true);
+ useEffect(() => {
+ (async () => {
+ if (loadOnce === false) {
+ loadOnce = true;
+ await setNobelPrizes();
+ setLoading(false);
+ }
+ })();
+ }, []);
+
+ if (loading) {
+ return <>Loading nobel prizes</>;
+ } else {
+ return <Router />;
+ }
+}
+
+export default App;
diff --git a/src/Router.tsx b/src/Router.tsx
new file mode 100644
index 0000000..eb4cf21
--- /dev/null
+++ b/src/Router.tsx
@@ -0,0 +1,30 @@
+import { useEffect, useState } from "react";
+import Search from "./Search";
+import Table from "./Table";
+
+const startPath =
+ location.pathname.at(-1) === "/"
+ ? location.pathname
+ : location.pathname + "/";
+export default function Router() {
+ const [path, setPath] = useState("");
+ useEffect(() => {
+ setPath(location.pathname);
+ }, []);
+ window.addEventListener("popstate", () => {
+ setPath(location.pathname);
+ });
+
+ function updatePath(newPath: string) {
+ console.log({ newPath, startPath });
+ const path = startPath + newPath;
+ history.pushState({}, "", path);
+ setPath(path);
+ }
+
+ if (path.includes("/nagrody/")) {
+ return <Table />;
+ } else {
+ return <Search setPath={updatePath} />;
+ }
+}
diff --git a/src/Search.tsx b/src/Search.tsx
new file mode 100644
index 0000000..cca6e42
--- /dev/null
+++ b/src/Search.tsx
@@ -0,0 +1,54 @@
+import { useEffect, useState } from "react";
+import { nobelPrizes } from "./data";
+
+export default function Search({ setPath }: { setPath: (a: string) => void }) {
+ const languages = { Angielski: "en", Norweski: "no", Szwedzki: "se" };
+ const languagesFlags = { Angielski: "🇬🇧", Norweski: "🇳🇴", Szwedzki: "🇸🇪" };
+ const langs = Object.keys(languages) as (keyof typeof languages)[];
+
+ const [language, setLanguage] = useState(langs[0]);
+ const [years, setYears] = useState<number[]>([]);
+ const [year, setYear] = useState<null | number>(null);
+
+ function changeLanguage() {
+ let i = langs.findIndex((l) => language === l) + 1;
+ if (i >= langs.length) i = 0;
+ setLanguage(langs[i]);
+ }
+
+ useEffect(() => {
+ setYears([...new Set(nobelPrizes.map((np) => parseInt(np.awardYear)))]);
+ }, []);
+
+ return (
+ <>
+ <h2>Wybierz z którego roku nagrody Nobla chcesz zobaczyć</h2>
+ <select
+ className="year-select"
+ onChange={(e) =>
+ setYear(e.target.value ? parseInt(e.target.value) : null)
+ }
+ >
+ <option value="">Wybierz rok</option>
+ {years.map((y) => (
+ <option value={y} key={y}>
+ {y}
+ </option>
+ ))}
+ </select>
+ <br />
+ <br />
+ <button
+ disabled={year === null}
+ onClick={() => setPath(`nagrody/${languages[language]}/${year}`)}
+ >
+ Wyszukaj nagrody
+ </button>
+ <div style={{ position: "absolute", top: 10, right: 10 }}>
+ <span onClick={changeLanguage}>
+ Język: {language} {languagesFlags[language]}
+ </span>
+ </div>
+ </>
+ );
+}
diff --git a/src/Table.tsx b/src/Table.tsx
new file mode 100644
index 0000000..26c08c7
--- /dev/null
+++ b/src/Table.tsx
@@ -0,0 +1,124 @@
+import { ReactNode, useEffect, useState } from "react";
+import { nobelPrizes } from "./data";
+
+export default function Table() {
+ const year = location.pathname.split("/").pop();
+ //@ts-expect-error Split exists on this type
+ const lang = location.pathname.match(/\/nagrody\/.*\//g)[0].split("/")[2] as
+ | "en"
+ | "no"
+ | "se";
+ const cols = {
+ awardYear: "Rok przyznania",
+ category: "Kategoria",
+ dateAwarded: "Data przyznania nagrody",
+ prizeAmount: "Kwota nagrody",
+ };
+ const colsKeys = Object.keys(cols) as (keyof typeof cols)[];
+ const [filterText, setFilterText] = useState("");
+ const [sortCol, setSortCol] = useState("awardYear");
+ //eslint-disable-next-line
+ let [sortDir, setSortDir] = useState(true);
+ const [trs, setTrs] = useState<ReactNode[]>([]);
+
+ function updateTrs(col: string, inverseDir = true) {
+ if (inverseDir && col === sortCol) sortDir = !sortDir;
+ if (col !== sortCol) setSortCol(col);
+
+ const npz = nobelPrizes.filter((np) => {
+ if (np.awardYear !== year) return false;
+ if (filterText === "") return true;
+
+ const ft = filterText.toLowerCase();
+
+ return (
+ np.awardYear.toLowerCase().includes(ft) ||
+ np.category[lang].toLowerCase().includes(ft) ||
+ formatDate(np.dateAwarded)?.toLowerCase().includes(ft) ||
+ np.prizeAmount.toString().toLowerCase().includes(ft)
+ );
+ });
+ npz.sort((npA, npB) => {
+ switch (col) {
+ case "awardYear":
+ return (
+ (parseInt(npA.awardYear) - parseInt(npB.awardYear)) *
+ (sortDir ? 1 : -1)
+ );
+ case "category":
+ return sortDir
+ ? npA.category[lang].localeCompare(npB.category[lang])
+ : npB.category[lang].localeCompare(npA.category[lang]);
+ case "dateAwarded":
+ return (
+ // @ts-expect-error Subtracting dates return the difference in their unix timestamps
+ (new Date(npA.dateAwarded) - new Date(npB.dateAwarded)) *
+ (sortDir ? 1 : -1)
+ );
+ case "prizeAmount":
+ return (npA.prizeAmount - npB.prizeAmount) * (sortDir ? 1 : -1);
+ default:
+ return 1;
+ }
+ });
+ let key = 0;
+ setTrs(
+ npz.map((np) => (
+ <tr key={key++}>
+ <td>{np.awardYear}</td>
+ <td>{np.category[lang]}</td>
+ <td>{formatDate(np.dateAwarded)}</td>
+ <td>
+ {np.prizeAmount
+ .toString()
+ .match(/.{1,3}/g)
+ ?.join(" ")}
+ &nbsp;kr
+ </td>
+ </tr>
+ )),
+ );
+ if (inverseDir && col === sortCol) setSortDir(sortDir);
+ }
+ function formatDate(date: string) {
+ return date ? new Date(date).toLocaleDateString("pl-PL") : "-";
+ }
+ useEffect(() => updateTrs(sortCol, false), [filterText]);
+
+ return (
+ <>
+ <h1>Nagrody nobla w roku {year}</h1>
+ <table>
+ <thead>
+ <tr>
+ {colsKeys.map((col) => (
+ <th key={col} onClick={() => updateTrs(col)}>
+ {cols[col]} &nbsp;
+ {sortCol === col && (sortDir ? <>🡅</> : <>🡇</>)}
+ </th>
+ ))}
+ </tr>
+ </thead>
+ <tbody>{trs}</tbody>
+ </table>
+ <br />
+ <br />
+ <label>
+ Filtruj&nbsp;
+ <input onChange={(e) => setFilterText(e.target.value)} />
+ </label>
+ <br />
+ <br />
+ <label>
+ Sortuj po:&nbsp;
+ <select onChange={(e) => updateTrs(e.target.value)}>
+ {colsKeys.map((col) => (
+ <option key={col} value={col}>
+ {cols[col]}
+ </option>
+ ))}
+ </select>
+ </label>
+ </>
+ );
+}
diff --git a/src/data.ts b/src/data.ts
new file mode 100644
index 0000000..116308d
--- /dev/null
+++ b/src/data.ts
@@ -0,0 +1,16 @@
+export let nobelPrizes: NobelPrize[] = [];
+export async function setNobelPrizes() {
+ nobelPrizes = (
+ await fetch("https://api.nobelprize.org/2.1/nobelPrizes").then((res) =>
+ res.json(),
+ )
+ ).nobelPrizes;
+}
+
+export interface NobelPrize {
+ awardYear: string;
+ category: { en: string; no: string; se: string };
+ dateAwarded: string;
+ prizeAmount: number;
+ prizeAmountAdjusted: number;
+}
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..503e8af
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,91 @@
+:root {
+ font-family: monospace;
+ line-height: 1.5;
+ font-weight: 400;
+ font-size: 20px;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-text-size-adjust: 100%;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 1.8rem;
+ line-height: 1.1;
+ font-weight: 550;
+}
+h2 {
+ font-size: 1.5rem;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
+
+#root {
+ margin: 0 auto;
+ text-align: center;
+}
+select,
+input {
+ padding: 0.2rem;
+}
+.year-select {
+ padding: 1rem;
+}
+span,
+th {
+ user-select: none;
+}
+
+th,
+td {
+ padding-inline: 1rem;
+ padding-block: 0.5rem;
+}
+
+table {
+ border-collapse: collapse;
+}
+td {
+ border-top: 1px solid white;
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..3d7150d
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App.tsx'
+import './index.css'
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+ <React.StrictMode>
+ <App />
+ </React.StrictMode>,
+)
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1 @@
+/// <reference types="vite/client" />