diff --git a/src/Vote.jsx b/src/Vote.jsx index 3caaea0..1fc54f5 100644 --- a/src/Vote.jsx +++ b/src/Vote.jsx @@ -10,7 +10,7 @@ import React, { useRef } from "react"; import { useLoaderData, useNavigate, useParams } from "react-router-dom"; import { db } from "./firebase"; -import { snackbar } from "mdui"; +import { confirm, snackbar } from "mdui"; import "./vote.css"; export default function Vote() { diff --git a/src/admin/Settings.jsx b/src/admin/Settings.jsx index 0e7236f..8540710 100644 --- a/src/admin/Settings.jsx +++ b/src/admin/Settings.jsx @@ -116,7 +116,17 @@ export default function Settings() {


Bei Problemen mit der Anzeige kann es helfen, den Cache zu leeren.

- localStorage.clear()} variant="tonal"> + { + localStorage.clear(); + snackbar({ + message: "Cache geleert", + action: "Neu laden", + onActionClick: () => window.location.reload(), + }); + }} + variant="tonal" + > Cache leeren

diff --git a/src/admin/index.css b/src/admin/index.css index a9d2fa4..0762d52 100644 --- a/src/admin/index.css +++ b/src/admin/index.css @@ -41,3 +41,41 @@ td, th { text-wrap: nowrap; } + +.print-table { + display: none; +} + +@media print { + .print-table { + display: block; + margin: 40px; + } + table { + width: 100%; + border-collapse: collapse; + } + + th, + td { + border: 1px solid #ddd; + padding: 8px; + } + + th { + background-color: #f2f2f2; + text-align: left; + } + + tr:nth-child(even) { + background-color: #f9f9f9; + } + + tr:hover { + background-color: #ddd; + } + tfoot { + background-color: #f2f2f2; + font-weight: bold; + } +} diff --git a/src/admin/vote/Answers.jsx b/src/admin/vote/Answers.jsx index de08ac0..beffb02 100644 --- a/src/admin/vote/Answers.jsx +++ b/src/admin/vote/Answers.jsx @@ -1,4 +1,12 @@ -import { collection, doc, getDoc, getDocs } from "firebase/firestore/lite"; +import { + collection, + deleteDoc, + doc, + getDoc, + getDocs, + setDoc, +} from "firebase/firestore/lite"; +import { confirm, prompt } from "mdui"; import React from "react"; import { useLoaderData } from "react-router-dom"; import { db } from "../../firebase"; diff --git a/src/admin/vote/Assign.jsx b/src/admin/vote/Assign.jsx index c66df1c..9058e90 100644 --- a/src/admin/vote/Assign.jsx +++ b/src/admin/vote/Assign.jsx @@ -69,6 +69,10 @@ export default function Assign() { console.log(data); setResults(data); + if (window.location.hostname === "localhost") { + console.log("Running on localhost"); + setLoading(false); + } setTimeout(() => setLoading(false), 5000); } catch (error) { console.error("Error fetching optimization:", error); @@ -299,7 +303,7 @@ export default function Assign() { setMode("by-option")}> - Nach Erstwahl + Nach Projekt setMode("by-grade")}> Nach Klasse @@ -320,14 +324,12 @@ export default function Assign() { key={i} value={option.id} > - {option.title} - - { - Object.values(results).filter( - (result) => result === option.id - ).length - } - + {option.title} ( + { + sortedResults.filter(([key, value]) => value === option.id) + .length + } + /{option.max}) ))} {options.map((option, i) => ( @@ -344,6 +346,14 @@ export default function Assign() { Klasse + {Array.from( + { length: vote.selectCount }, + (_, i) => i + 1 + ).map((i) => ( + + Wahl {i} + + ))} @@ -360,6 +370,136 @@ export default function Assign() { .grade } + {choices + .find((choice) => choice.id === key) + .selected.map((selected, i) => ( + { + if (selected === value) return; + const newResults = { ...results }; + newResults[key] = selected; + setResults(newResults); + const previousResults = { ...results }; + snackbar({ + message: "Änderung rückgängig machen", + action: "Rückgängig", + onActionClick: () => + setResults(previousResults), + }); + }} + > + {selected === value + ? "✓" + : `${ + options.find( + (option) => option.id === selected + ).title + } (${ + sortedResults.filter( + ([key, value]) => value === selected + ).length + }/${ + options.find( + (option) => option.id === selected + ).max + })`} + + ))} + + ))} + + + +

Alle Wähler

+
+ + + + + + + {Array.from( + { length: vote.selectCount }, + (_, i) => i + 1 + ).map((i) => ( + + ))} + + + + {choices + .filter((choice) => choice.selected.includes(option.id)) + .sort((a, b) => a.name.localeCompare(b.name)) + .map((choice, i) => ( + + + + + {choice.selected.map((selected, i) => ( + + ))} ))} diff --git a/src/admin/vote/Results.jsx b/src/admin/vote/Results.jsx new file mode 100644 index 0000000..a4bec9b --- /dev/null +++ b/src/admin/vote/Results.jsx @@ -0,0 +1,186 @@ +import { + collection, + doc, + getDoc, + getDocs, + setDoc, +} from "firebase/firestore/lite"; +import { useLoaderData } from "react-router-dom"; +import { auth, db } from "../../firebase"; + +export default function Results() { + const { vote, options, results, choices } = useLoaderData(); + + function printResults() { + const printContents = document.querySelector(".print-table").outerHTML; + const originalContents = document.body.innerHTML; + document.body.innerHTML = printContents; + window.print(); + document.body.innerHTML = originalContents; + window.location.reload(); + } + + function publishResults() { + setDoc( + doc(db, `votes/${vote.id}`), + { + result: true, + }, + { merge: true } + ); + } + + const sortedResults = () => { + // sort by grade + let resultsByGrade = {}; + choices.forEach((choice) => { + if (!results.find((result) => result.id === choice.id)) { + return; + } + if (!resultsByGrade[choice.grade]) { + resultsByGrade[choice.grade] = []; + } + resultsByGrade[choice.grade].push({ + ...results.find((result) => result.id === choice.id), + name: choice.name, + }); + }); + // then sort by name + Object.keys(resultsByGrade).forEach((grade) => { + resultsByGrade[grade].sort((a, b) => { + return choices + .find((choice) => choice.id === a.id) + .name.localeCompare( + choices.find((choice) => choice.id === b.id).name + ); + }); + }); + + // Convert resultsByGrade object to a list + let resultsList = []; + Object.keys(resultsByGrade).forEach((grade) => { + resultsByGrade[grade].forEach((result) => { + resultsList.push({ + ...result, + grade: grade, + }); + }); + }); + return resultsList; + }; + return ( +
+

Ergebnisse

+
+
+ Name + + Klasse + + # + + Wahl {i} +
{choice.name}{choice.grade}{choice.listIndex} { + if (results[choice.id] === selected) return; + const newResults = { ...results }; + newResults[choice.id] = selected; + setResults(newResults); + const previousResults = { ...results }; + snackbar({ + message: "Änderung rückgängig machen", + action: "Rückgängig", + onActionClick: () => + setResults(previousResults), + }); + }} + > + { + options.find( + (option) => option.id === selected + ).title + } + + {results[choice.id] === selected && + ` (${ + sortedResults.filter( + ([key, value]) => value === selected + ).length + }/${ + options.find( + (option) => option.id === selected + ).max + }) ✓`} +
+ + + + + + + + + + {sortedResults().map((result) => ( + + + + + + + ))} + +
NameKlasse#Projekt
+ {choices.find((choice) => choice.id === result.id).name} + + {choices.find((choice) => choice.id === result.id).grade} + + {choices.find((choice) => choice.id === result.id).listIndex} + + {options.find((option) => option.id === result.result).title} +
+
+
+

{vote.title}

+ + <> + + + + + + + + {sortedResults().map((result) => ( + + + + + + ))} + + <> + + + + +
KlasseNameProjekt
+ {choices.find((choice) => choice.id === result.id).name} + + {choices.find((choice) => choice.id === result.id).grade} + + {options.find((option) => option.id === result.result).title} +
+ + Generiert am {new Date().toLocaleDateString()} von{" "} + {auth.currentUser.email} mit WaldorfWahlen + +
+
+
+ + Drucken + + + + Veröffentlichen + + +
+ + ); +} + +export async function loader({ params }) { + const { id } = params; + const vote = (await getDoc(doc(db, `/votes/${id}`))).data(); + const options = ( + await getDocs(collection(db, `/votes/${id}/options`)) + ).docs.map((doc) => { + return { id: doc.id, ...doc.data() }; + }); + + const results = ( + await getDocs(collection(db, `/votes/${id}/results`)) + ).docs.map((doc) => { + return { id: doc.id, ...doc.data() }; + }); + + const choices = ( + await getDocs(collection(db, `/votes/${id}/choices`)) + ).docs.map((doc) => { + return { id: doc.id, ...doc.data() }; + }); + + return { + vote, + options, + results, + choices, + }; +} diff --git a/src/main.jsx b/src/main.jsx index fdcbf73..fa31860 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -25,6 +25,7 @@ import Delete from "./admin/vote/Delete.jsx"; import Edit from "./admin/vote/Edit.jsx"; import Export from "./admin/vote/Export.jsx"; import AdminVote, { loader as adminVoteLoader } from "./admin/vote/index.jsx"; +import Results, { loader as resultsLoader } from "./admin/vote/Results.jsx"; import Schedule from "./admin/vote/Schedule.jsx"; import Share from "./admin/vote/Share.jsx"; @@ -123,6 +124,11 @@ const router = createBrowserRouter([ path: "export", element: , }, + { + path: "results", + element: , + loader: resultsLoader, + }, ], }, ],