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
+
+
+
+
+
+ Name
+ |
+
+ Klasse
+ |
+
+ #
+ |
+ {Array.from(
+ { length: vote.selectCount },
+ (_, i) => i + 1
+ ).map((i) => (
+
+ Wahl {i}
+ |
+ ))}
+
+
+
+ {choices
+ .filter((choice) => choice.selected.includes(option.id))
+ .sort((a, b) => a.name.localeCompare(b.name))
+ .map((choice, i) => (
+
+ {choice.name} |
+ {choice.grade} |
+ {choice.listIndex} |
+ {choice.selected.map((selected, i) => (
+ {
+ 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
+ }) ✓`}
+ |
+ ))}
))}
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 |
+ # |
+ Projekt |
+
+
+
+ {sortedResults().map((result) => (
+
+
+ {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}
+
+ <>
+
+ Klasse |
+ Name |
+ Projekt |
+
+ >
+
+ {sortedResults().map((result) => (
+
+
+ {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,
+ },
],
},
],