Skip to content

Commit

Permalink
Add release notes feature, enhance error handling, and update styles (#…
Browse files Browse the repository at this point in the history
…31)

* Add release notes feature, enhance error handling, and update styles

* ReleaseNotes Error-catching

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update src/admin/index.jsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Add loading state to publish release notes and handle errors

* Merge branch 'feat/help' of https://github.com/johangrims/waldorfwahlen into feat/help

* Add new menu item for changelog and refactor email display logic

* Update navigation to replace 'features' with 'changelog' and adjust routing accordingly
  • Loading branch information
JohanGrims authored Dec 7, 2024
1 parent c75fc5d commit ce7636f
Show file tree
Hide file tree
Showing 12 changed files with 915 additions and 7 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"react-ace": "^10.1.0",
"react-csv": "^2.2.2",
"react-dom": "^18.2.0",
"react-markdown": "^9.0.1",
"react-qr-code": "^2.0.12",
"react-router-dom": "^6.21.0",
"react-table": "^7.8.0",
Expand Down
661 changes: 661 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Error.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function ErrorPage() {
return (
<mdui-dialog open headline={`Fehler ${error?.status || "400"}`}>
<div className="mdui-prose">
<p>{error?.statusText || "Unknown Error Message"}</p>
<p>{error?.statusText || error?.data || "Unknown Error Message"}</p>
<p>
{error.status >= 400 && error.status < 500
? "Es scheint, dass der Fehler auf Ihrer Seite liegt."
Expand Down
64 changes: 64 additions & 0 deletions src/admin/CreateReleaseNotes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { doc, setDoc } from "firebase/firestore";
import { snackbar } from "mdui";
import React from "react";
import Markdown from "react-markdown";
import { useLoaderData } from "react-router-dom";
import { db } from "../firebase";

export default function CreateReleaseNotes() {
const { releaseNotes } = useLoaderData() as {
releaseNotes: { content: string };
};
const [content, setContent] = React.useState(releaseNotes.content);

const [publishing, setPublishing] = React.useState(false);

async function publishReleaseNotes() {
setPublishing(true);
setDoc(doc(db, "docs", "release-notes"), { content })
.then(() => {
snackbar({ message: "Gespeichert!" });
setPublishing(false);
})
.catch(() => {
snackbar({ message: "Fehler beim Speichern" });
setPublishing(false);
});
}
return (
<div className="mdui-prose">
<mdui-tabs value="edit">
<mdui-tab value="edit">Bearbeiten</mdui-tab>
<mdui-tab value="preview">Vorschau</mdui-tab>

<mdui-tab-panel slot="panel" value="edit">
<p />
<mdui-text-field
variant="outlined"
autosize
min-rows={15}
label="Inhalt"
value={content}
onInput={(e) => setContent((e.target as HTMLInputElement).value)}
></mdui-text-field>
</mdui-tab-panel>
<mdui-tab-panel slot="panel" value="preview">
<p />
<Markdown className={"help"}>{content}</Markdown>
</mdui-tab-panel>
</mdui-tabs>

<div style={{ position: "fixed", bottom: "1rem", right: "1rem" }}>
{publishing ? (
<mdui-fab icon="public" loading extended>
Veröffentlichen
</mdui-fab>
) : (
<mdui-fab icon="public" extended onClick={publishReleaseNotes}>
Veröffentlichen
</mdui-fab>
)}
</div>
</div>
);
}
63 changes: 63 additions & 0 deletions src/admin/Help.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { doc, getDoc } from "firebase/firestore";
import React from "react";
import Markdown from "react-markdown";
import { useLoaderData } from "react-router-dom";
import { db } from "../firebase";

export default function Help() {
const { helpContent } = useLoaderData() as {
helpContent: { content: string };
};

return (
<div className="mdui-prose">
<h1>Hilfe & Kontakt</h1>
<Markdown className="help">
{`
## Einleitung
WaldorfWahlen ist eine Webanwendung, die der [Waldorfschule Potsdam](https://waldorfschule-potsdam.de) ermöglicht, Projektwahlen für Schülerinnen und Schüler durchzuführen und über ein Admin-Dashboard auszuwerten.
## Ablauf
1. **Wahl erstellen**: Loggen Sie sich unter [waldorfwahlen.web.app/admin](https://waldorfwahlen.web.app/admin) ein und klicken Sie auf "Erstellen".
2. **Wahl konfigurieren**: Geben Sie der Wahl einen Titel, fügen Sie optional eine Beschreibung hinzu und legen Sie die Anzahl der Wahlen pro SchülerIn fest. Fügen Sie mit "Extrafeld hinzufügen" weitere Felder hinzu, die die SchülerInnen ausfüllen müssen.
3. **Zeitraum festlegen**: Legen Sie den Zeitraum fest, in dem die Wahl stattfinden soll. Wählen Sie Start- und Enddatum sowie Start- und Endzeit.
4. **Optionen hinzufügen**: Fügen Sie die Optionen hinzu, die die SchülerInnen wählen können. Neben dem Titel müssen Sie eine Maximalanzahl an SchülerInnen festlegen, die diese Option wählen können. Optional können Sie den Lehrer und eine Beschreibung hinzufügen.
5. **Wahl starten**: Klicken Sie auf "Erstellen". Die Wahl ist nun aktiv und die SchülerInnen können in dem von Ihnen gewählten Zeitraum Projekte auswählen. Sie können die Wahl nun unter "Geplante Wahlen" anklicken, um zu den Optionen zu gelangen.
6. **Vorschau**: Klicken Sie auf "Vorschau", um die Wahl aus Sicht der SchülerInnen zu sehen.
7. **Teilen**: Teilen Sie den Link zur Wahl mit den SchülerInnen. Sie können einen QR-Code generieren, um den Link auszudrucken. Über die Einstellungen können Sie eine Mehrfachwahl erlauben.
8. **Bearbeiten**: Sie können unter "Bearbeiten" den Titel und die Beschreibung der Wahl ändern und die Optionen bearbeiten. Beachten Sie, dass gelöschte Optionen bei laufender Wahl zu Problemen führen können.
9. **Zeitraum ändern**: Sie können den Zeitraum der Wahl unter "Planen" ändern. Außerdem ist es möglich die Wahl zu deaktivieren, um sie zu einem späteren Zeitpunkt wieder zu aktivieren.
10. **Antworten**: Unter "Antworten" sehen Sie die Antworten der SchülerInnen. Über die verschiedenen Optionen können Sie die Antworten nach Erstwahl oder Klasse sortieren. "Nach Name" zeigt alle Antworten der SchülerInnen als Tabelle an. So können Sie nach SchülerInnen suchen und Antworten löschen.
11. **Abgleichen**: Unter "Abgleichen" können Sie die Antworten der SchülerInnen mit den Klassenlisten abgleichen. So können Sie sehen, welche SchülerInnen noch nicht gewählt haben. "Nach Datenbank" zeigt alle SchülerInnen an, die in der Datenbank sind. "Antworten" zeigt alle SchülerInnen an, die gewählt haben. Die Einträge können angeklickt werden, was die Antwort der SchülerInnen anzeigt.
12. **Zuteilen**: In der Automatischen Optimierung können Sie die SchülerInnen automatisch auf die Optionen verteilen. Über "Regeln anpassen" könne Sie hierfür Gewichtungen festlegen (Beispiel: SchülerInnen der Klasse 12 haben Vorrang). Über "Manuelle Zuordnung" können Sie die SchülerInnen auf Ihre Erstwahl setzen.
13. **Anpassen**: Nun können Sie die Zuteilung der SchülerInnen anpassen. Je nach Ansicht sehen Sie verschiedene Tabellen, die die SchülerInnen und Optionen anzeigen. Sie können die SchülerInnen durch Klicken auf den blauen Link zu einer anderen Option verschieben.
14. **Zuteilung speichern**: Durch Klicken auf "Ergebnisse speichern" werden die Zuteilungen in der Datenbank gespeichert. Sie können jederzeit überschrieben werden.
15. **Ergebnisse**: Unter "Ergebnisse" sehen Sie die Zuteilung der SchülerInnen. Über die verschiedenen Optionen können Sie die Ergebnisse nach Klasse oder Option sortieren. Drucken Sie die Ergebnisse aus, um sie an die LehrerInnen zu verteilen. Die Schaltfläche "Ergebnisse veröffentlichen" publiziert die Ergebnisse sicher für die SchülerInnen. Dafür müssen die SchülerInnen das Ergebnis auf demselben Gerät sehen, auf dem sie gewählt haben.
16. **Exportieren**: Sie können alle Daten einer Wahl als Excel-Datei exportieren. Wählen Sie dazu die gewünschte Wahl aus und klicken Sie auf "Exportieren". Für die korrekte Ansicht müssen Sie die Datei in Excel öffnen und gewisse XVerweise setzen.
17. **Wahl löschen**: Sie können eine Wahl unter "Löschen" ausblenden. Die Daten bleiben in der Datenbank erhalten, können aber nicht mehr eingesehen werden. Bei Bedarf zur Wiederherstellung wenden Sie sich bitte an mich.
---
## SchülerInnen
Die SchülerInnen-Datenbank enthält die Klassenlisten. Die SchülerInnen können in der Datenbank eingesehen und bearbeitet werden. Neue Klassen können über hinzufügen als Excel-Datei hochgeladen werden. Die SchülerInnen können über die Schaltfläche "Bearbeiten" bearbeitet werden.
- Excel-Formar: Bitte stellen Sie sicher, dass die Datei **im .xlsx-Format** vorliegt und das Format wie folgt ist: 1. Zeile — name | listIndex als Überschrift, danach für jede Zeile die individuellen Daten. Die Reihenfolge der Spalten ist nicht relevant. Es wird immer nur das erste Tabellenblatt gelesen.
- Mit **"Schuljahr wechseln"** werden alle Klassennummern um eins erhöht. Dies ist nützlich, wenn ein neues Schuljahr beginnt.
- Mit dem Stift-Symbol können die **Klassen bearbeitet** werden. Hier könne Sie die SchülerInnen als JSON-Datei bearbeiten oder über "Hochladen" eine Excel-Datei hochladen.
- Das Feld listIndex in der Datenbank gibt die **Reihenfolge der SchülerInnen** in der Klassenliste an.
## Einstellungen
Hier können Sie Ihren Account anpassen. Sie können Ihr Passwort ändern und vom standardmäßigen Darkmode auf den Lightmode wechseln. Der Darkmode ist für die Augen schonender und wird empfohlen. Bei Problemen kann es helfen, den Cache zu leeren.
${
helpContent?.content ||
"Haben Sie Fragen oder Probleme? Wenden Sie sich bitte an mich."
}
`}
</Markdown>
</div>
);
}

Help.loader = async function loader() {
const helpContent = await getDoc(doc(db, "docs", "help")); // Get contact information securely from Firestore (only accessible to admins)
return { helpContent: helpContent.data() };
};
36 changes: 36 additions & 0 deletions src/admin/ReleaseNotes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { doc, getDoc } from "firebase/firestore";
import { db } from "../firebase";

import React from "react";
import Markdown from "react-markdown";
import { useLoaderData } from "react-router-dom";

export default function ReleaseNotes() {
const { releaseNotes } = useLoaderData() as {
releaseNotes: { content: string };
};
return (
<div className="mdui-prose">
<h1>Neuigkeiten 🎉</h1>
<Markdown className="help">{releaseNotes.content}</Markdown>
</div>
);
}
ReleaseNotes.loader = async function loader() {
try {
const releaseNotesData = await getDoc(doc(db, "docs", "release-notes"));
if (!releaseNotesData.exists()) {
throw new Error('Release notes not found');
}
const data = releaseNotesData.data();
if (!data?.content) {
throw new Error('Invalid release notes format');
}
return { releaseNotes: data };
} catch (error) {
throw new Response('Failed to load release notes', {
status: error.message === 'Release notes not found' ? 404 : 500,
statusText: error.message
});
}
};
40 changes: 36 additions & 4 deletions src/admin/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { auth } from "../firebase";
import "./index.css";

import { confirm, snackbar } from "mdui";
import { useNavigate } from "react-router-dom";
import Login from "./auth/Login";
import DrawerList from "./navigation/DrawerList";

Expand All @@ -15,6 +16,8 @@ export default function Admin(props) {

const [open, setOpen] = React.useState(window.innerWidth > 1024);

const navigate = useNavigate();

React.useEffect(() => {
const listen = onAuthStateChanged(auth, (user) => {
if (user) {
Expand Down Expand Up @@ -66,10 +69,39 @@ export default function Admin(props) {
></mdui-button-icon>
)}
<mdui-top-app-bar-title>{authUser.email}</mdui-top-app-bar-title>
<mdui-avatar style={{ marginRight: "1rem" }}>
{authUser.email.split(".")[0].charAt(0).toUpperCase()}
{authUser.email.split(".")[1]?.charAt(0).toUpperCase()}
</mdui-avatar>

<mdui-dropdown>
<mdui-avatar
slot="trigger"
style={{ marginRight: "1rem", cursor: "pointer" }}
>
{authUser.email
.split(/[@.]/)
.slice(0, 2)
.map((part) => part.charAt(0).toUpperCase())
.join("")}
</mdui-avatar>
<mdui-menu>
<mdui-menu-item
icon="settings"
onClick={() => navigate("/admin/settings")}
>
Einstellungen
</mdui-menu-item>
<mdui-menu-item
icon="tips_and_updates"
onClick={() => navigate("/admin/changelog")}
>
Neue Features
</mdui-menu-item>
<mdui-menu-item
icon="support"
onClick={() => navigate("/admin/help")}
>
Hilfe & Kontakt
</mdui-menu-item>
</mdui-menu>
</mdui-dropdown>

<mdui-tooltip content="Abmelden" open-delay="0" placement="left">
<mdui-button-icon
Expand Down
30 changes: 29 additions & 1 deletion src/admin/navigation/DrawerList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { db } from "../../firebase";
import { DrawerItem } from "./components";
import VoteDrawer from "./VoteDrawer";

let pages = [undefined, "new", "settings", "students"];
let pages = [undefined, "new", "settings", "students", "help", "changelog"];

export default function DrawerList() {
const [activeVotes, setActiveVotes] = React.useState([]);
Expand Down Expand Up @@ -148,6 +148,7 @@ export default function DrawerList() {
})
.map((e) => (
<mdui-tooltip
key={e.id}
variant="rich"
headline="Wahl bearbeiten"
content="Bearbeiten Sie die Wahl, setzen Sie die Einstellungen und weisen Sie Schüler zu."
Expand Down Expand Up @@ -187,6 +188,7 @@ export default function DrawerList() {
})
.map((e) => (
<mdui-tooltip
key={e.id}
variant="rich"
headline="Wahl bearbeiten"
content="Bearbeiten Sie die Wahl, setzen Sie die Einstellungen und weisen Sie Schüler zu."
Expand Down Expand Up @@ -226,6 +228,7 @@ export default function DrawerList() {
})
.map((e) => (
<mdui-tooltip
key={e.id}
variant="rich"
headline="Wahl bearbeiten"
content="Bearbeiten Sie die Wahl, setzen Sie die Einstellungen und weisen Sie Schüler zu."
Expand Down Expand Up @@ -256,6 +259,31 @@ export default function DrawerList() {

<mdui-divider />
<br />
<mdui-tooltip
variant="rich"
headline="Neue Features"
content="Sehen Sie sich die neuesten Funktionen an."
>
<DrawerItem
active={active === "changelog"}
title={"Neue Features"}
icon={"tips_and_updates"}
onCLick={() => navigate("/admin/changelog")}
/>
</mdui-tooltip>

<mdui-tooltip
variant="rich"
headline="Hilfe & Kontakt"
content="Kontaktieren Sie den Entwickler, um Hilfe zu erhalten."
>
<DrawerItem
active={active === "help"}
title={"Hilfe & Kontakt"}
icon={"support"}
onCLick={() => navigate("/admin/help")}
/>
</mdui-tooltip>

<mdui-tooltip
variant="rich"
Expand Down
1 change: 1 addition & 0 deletions src/admin/vote/Edit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ Edit.loader = async function loader({ params }) {
id: doc.id,
...doc.data(),
}));
console.log("Loaded options:", optionData);

return {
vote: voteData,
Expand Down
2 changes: 1 addition & 1 deletion src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,4 @@ input:focus,
input.button {
color: black;
}
}
}
18 changes: 18 additions & 0 deletions src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ import Scheduled from "./Scheduled";
import Submitted from "./Submitted";
import Vote from "./Vote";

import CreateReleaseNotes from "./admin/CreateReleaseNotes";
import Help from "./admin/Help";
import Admin from "./admin/index";
import NewVote from "./admin/NewVote";
import Overview from "./admin/Overview";
import ReleaseNotes from "./admin/ReleaseNotes";
import Settings from "./admin/Settings";
import Students from "./admin/Students";
import Answers from "./admin/vote/Answers";
Expand Down Expand Up @@ -91,6 +94,21 @@ const routes = [
path: "settings",
element: <Settings />,
},
{
path: "help",
element: <Help />,
loader: Help.loader,
},
{
path: "changelog",
element: <ReleaseNotes />,
loader: ReleaseNotes.loader,
},
{
path: "changelog/edit",
element: <CreateReleaseNotes />,
loader: ReleaseNotes.loader,
},
{
path: ":id/*",
children: [
Expand Down
4 changes: 4 additions & 0 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,10 @@ input::-webkit-inner-spin-button {
margin: 0;
}

.help > h2{
margin-bottom: 10px !important;
}

@media only screen and (max-width: 600px) {
.vote {
width: 100%;
Expand Down

0 comments on commit ce7636f

Please sign in to comment.