refactor: seperate code into different files

This commit is contained in:
Patryk Hegenberg 2024-11-28 19:12:37 +01:00
parent 2f6f0b4efd
commit 997cc11a89
16 changed files with 845 additions and 364 deletions

BIN
Icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

188
app.go Normal file
View file

@ -0,0 +1,188 @@
package main
import (
"context"
"fmt"
"strconv"
"github.com/jung-kurt/gofpdf"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
type App struct {
ctx context.Context
bewertungen []Bewertung
maxPunkte MaxPunkte
}
func NewApp() *App {
return &App{
bewertungen: make([]Bewertung, 0),
maxPunkte: MaxPunkte{
HvMax: 0.00,
HvGewichtung: 0.00,
LvMax: 0.00,
LvGewichtung: 0.00,
},
}
}
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}
func (a *App) OpenSaveDialog() (string, error) {
return runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{
DefaultFilename: "bewertungen.pdf",
Filters: []runtime.FileFilter{
{DisplayName: "PDF Files (*.pdf)", Pattern: "*.pdf"},
},
})
}
func (a *App) GetBewertungen() []Bewertung {
return a.bewertungen
}
func (a *App) GetMaxPunkte() MaxPunkte {
return a.maxPunkte
}
func (a *App) ToggleWertung(id int) Bewertung {
var updatedBewertung Bewertung
for i, bewertung := range a.bewertungen {
if bewertung.ID == id {
a.bewertungen[i].Gewertet = !bewertung.Gewertet
updatedBewertung = a.bewertungen[i]
break
}
}
return updatedBewertung
}
func (a *App) AddBewertung(vorname, nachname string, hvPunkte, lvPunkte float64) bool {
if !a.validateName(vorname, nachname) {
return false
}
hvProzent := 100.00 / a.maxPunkte.HvMax * hvPunkte
lvProzent := 100.00 / a.maxPunkte.LvMax * lvPunkte
hvNote := setNote(hvProzent)
lvNote := setNote(lvProzent)
gesamtProzent := hvProzent*a.maxPunkte.HvGewichtung/100 + lvProzent*a.maxPunkte.LvGewichtung/100
gesamtNote := setNote(gesamtProzent)
bewertung := Bewertung{
ID: len(a.bewertungen) + 1,
Vorname: vorname,
Nachname: nachname,
HvPunkte: hvPunkte,
HvProzent: hvProzent,
HvNote: int(hvNote),
LvPunkte: lvPunkte,
LvProzent: lvProzent,
LvNote: int(lvNote),
GesamtProzent: gesamtProzent,
GesamtNote: int(gesamtNote),
Gewertet: true,
}
a.bewertungen = append(a.bewertungen, bewertung)
return true
}
func (a *App) SetMaxPunkte(hvMax, lvMax, hvGewichtung, lvGewichtung float64) bool {
if !checkGewichtung(lvGewichtung, hvGewichtung) {
return false
}
a.maxPunkte = MaxPunkte{
HvMax: hvMax,
LvMax: lvMax,
HvGewichtung: hvGewichtung,
LvGewichtung: lvGewichtung,
}
return true
}
func (a *App) ExportBewertungen(path string) error {
pdf := gofpdf.New("P", "mm", "A4", "")
pdf.AddPage()
// Bewertungen exportieren
pdf.SetFont("Arial", "B", 16)
pdf.CellFormat(0, 10, "Bewertungen", "", 1, "C", false, 0, "")
pdf.Ln(5)
pdf.SetFont("Arial", "B", 12)
pdf.CellFormat(27, 10, "Vorname", "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, "Nachname", "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, "HV-Punkte", "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, "HV-Note", "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, "LV-Punkte", "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, "LV-Note", "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, "Gesamtnote", "1", 0, "", false, 0, "")
pdf.Ln(-1)
pdf.SetFont("Arial", "", 11)
for _, bewertung := range a.bewertungen {
if bewertung.Gewertet {
pdf.CellFormat(27, 10, bewertung.Vorname, "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, bewertung.Nachname, "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, strconv.FormatFloat(bewertung.HvPunkte, 'f', 2, 64), "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, strconv.FormatInt(int64(bewertung.HvNote), 10), "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, strconv.FormatFloat(bewertung.LvPunkte, 'f', 2, 64), "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, strconv.FormatInt(int64(bewertung.LvNote), 10), "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, strconv.FormatInt(int64(bewertung.GesamtNote), 10), "1", 0, "", false, 0, "")
pdf.Ln(-1)
}
}
pdf.AddPage()
pdf.SetFont("Arial", "B", 16)
pdf.CellFormat(0, 10, "Notenspiegel", "", 1, "C", false, 0, "")
pdf.Ln(5)
notenspiegel := a.GetNotenspiegel()
pdf.SetFont("Arial", "B", 12)
pdf.CellFormat(30, 10, "Note", "1", 0, "", false, 0, "")
pdf.CellFormat(30, 10, "Anzahl", "1", 0, "", false, 0, "")
pdf.Ln(-1)
pdf.SetFont("Arial", "", 11)
for note := 1; note <= 6; note++ {
anzahl := notenspiegel[note]
pdf.CellFormat(30, 10, strconv.Itoa(note), "1", 0, "", false, 0, "")
pdf.CellFormat(30, 10, strconv.Itoa(anzahl), "1", 0, "", false, 0, "")
pdf.Ln(-1)
}
err := pdf.OutputFileAndClose(path)
if err != nil {
fmt.Println("Fehler beim Exportieren der Bewertungen:", err)
return err
}
runtime.EventsEmit(a.ctx, "export-complete")
return nil
}
func (a *App) GetNotenspiegel() map[int]int {
notenspiegel := make(map[int]int)
for _, bewertung := range a.bewertungen {
notenspiegel[bewertung.GesamtNote]++
}
return notenspiegel
}
func (a *App) validateName(vorname, nachname string) bool {
for _, bewertung := range a.bewertungen {
if bewertung.Nachname == nachname && bewertung.Vorname == vorname {
return false
}
}
return true
}

View file

@ -1,12 +1,25 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" /> <meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>Noten</title> <title>Noten</title>
<link href="https://cdn.jsdelivr.net/npm/daisyui@4.12.14/dist/full.min.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@catppuccin/daisyui@1.2.1/dist/catppuccin.css" />
<script src="https://cdn.tailwindcss.com"></script>
</head> </head>
<body> <body>
<!-- <div data-theme="macchiato" id="app"></div> -->
<div id="app"></div> <div id="app"></div>
<script>
// Set initial theme based on user preference or default
const initialTheme = localStorage.getItem("theme") || "macchiato";
document.getElementById("app").setAttribute("data-theme", initialTheme);
</script>
<script src="./src/main.js" type="module"></script> <script src="./src/main.js" type="module"></script>
</body> </body>
</html> </html>

View file

@ -1,19 +1,32 @@
<script> <script>
import { onMount } from 'svelte'; import { onMount } from "svelte";
import { AddBewertung, GetBewertungen, GetMaxPunkte, SetMaxPunkte, ToggleWertung, ExportBewertungen, GetNotenspiegel } from '../wailsjs/go/main/App'; import {
import { OpenSaveDialog } from '../wailsjs/go/main/App'; AddBewertung,
import 'bulma/css/bulma.min.css'; GetBewertungen,
GetMaxPunkte,
SetMaxPunkte,
ToggleWertung,
ExportBewertungen,
GetNotenspiegel,
} from "../wailsjs/go/main/App";
import { OpenSaveDialog } from "../wailsjs/go/main/App";
import MaxPunkteForm from "./MaxPunkteForm.svelte";
import BewertungForm from "./BewertungForm.svelte";
import BewertungenTable from "./BewertungenTable.svelte";
import Notenspiegel from "./Notenspiegel.svelte";
import ExportSection from "./ExportSection.svelte";
import ThemeSwitcher from "./ThemeSwitcher.svelte";
let bewertungen = []; let bewertungen = [];
let maxPunkte = { let maxPunkte = {
hvMax: 0, hvMax: 0,
lvMax: 0, lvMax: 0,
hvGewichtung: 0, hvGewichtung: 0,
lvGewichtung: 0 lvGewichtung: 0,
}; };
let vorname = ''; let vorname = "";
let nachname = ''; let nachname = "";
let hvPunkte = 0; let hvPunkte = 0;
let lvPunkte = 0; let lvPunkte = 0;
let hvMax = 0; let hvMax = 0;
@ -21,8 +34,8 @@
let hvGewichtung = 0; let hvGewichtung = 0;
let lvGewichtung = 0; let lvGewichtung = 0;
let exportPath = ''; let exportPath = "";
let notenspiegel = {} let notenspiegel = {};
onMount(async () => { onMount(async () => {
await loadData(); await loadData();
@ -32,22 +45,24 @@
async function loadData() { async function loadData() {
bewertungen = await GetBewertungen(); bewertungen = await GetBewertungen();
maxPunkte = await GetMaxPunkte(); maxPunkte = await GetMaxPunkte();
if (maxPunkte.hvMax === 0) {
hvMax = maxPunkte.hvMax; hvMax = maxPunkte.hvMax;
lvMax = maxPunkte.lvMax; lvMax = maxPunkte.lvMax;
hvGewichtung = maxPunkte.hvGewichtung; hvGewichtung = maxPunkte.hvGewichtung;
lvGewichtung = maxPunkte.lvGewichtung; lvGewichtung = maxPunkte.lvGewichtung;
} }
}
async function handleAddBewertung() { async function handleAddBewertung() {
if (maxPunkte.hvMax !== 0 && maxPunkte.hvGewichtung !== 0) {
const success = await AddBewertung(vorname, nachname, hvPunkte, lvPunkte); const success = await AddBewertung(vorname, nachname, hvPunkte, lvPunkte);
if (success) { if (success) {
await loadData(); await loadData();
resetForm(); resetForm();
await loadNotenspiegel(); await loadNotenspiegel();
} else { } else {
alert('Name existiert bereits!'); alert("Name existiert bereits!");
}
} else {
alert("Max Punkte muss befüllt sein");
} }
} }
@ -55,12 +70,37 @@
notenspiegel = await GetNotenspiegel(); notenspiegel = await GetNotenspiegel();
} }
async function handleSetMaxPunkte() { let isMaxPunkteValid = false;
const success = await SetMaxPunkte(hvMax, lvMax, hvGewichtung, lvGewichtung);
if (!success) { function validateMaxPunkte() {
alert('Ungültige Gewichtung! Die Summe muss 100% ergeben.'); if (hvMax > 0 && lvMax === 0) {
isMaxPunkteValid = hvGewichtung === 100;
} else if (hvMax > 0 && lvMax > 0) {
isMaxPunkteValid = hvGewichtung + lvGewichtung === 100;
} else { } else {
isMaxPunkteValid = false;
}
}
$: {
validateMaxPunkte();
}
async function handleSetMaxPunkte() {
validateMaxPunkte();
if (isMaxPunkteValid) {
const success = await SetMaxPunkte(
hvMax,
lvMax,
hvGewichtung,
lvGewichtung,
);
if (success) {
await loadData(); await loadData();
} else {
alert("Es gab einen Fehler beim Setzen der Max-Punkte.");
}
} }
} }
@ -75,149 +115,68 @@
exportPath = result; exportPath = result;
} }
} catch (error) { } catch (error) {
console.error('Fehler beim Öffnen des Dateiauswahldialogs:', error); console.error("Fehler beim Öffnen des Dateiauswahldialogs:", error);
} }
} }
async function handleExport() { async function handleExport() {
if (!exportPath) { if (!exportPath) {
alert('Bitte wählen Sie einen Speicherpfad aus.'); alert("Bitte wählen Sie einen Speicherpfad aus.");
return; return;
} }
await ExportBewertungen(exportPath); await ExportBewertungen(exportPath);
alert('Export abgeschlossen!'); alert("Export abgeschlossen!");
} }
function resetForm() { function resetForm() {
vorname = ''; vorname = "";
nachname = ''; nachname = "";
hvPunkte = 0; hvPunkte = 0;
lvPunkte = 0; lvPunkte = 0;
} }
</script> </script>
<div class="container is-widescreen"> <div class="container mx-auto p-4">
<div class="card"> <!-- <ThemeSwitcher /> -->
<header class="card-header"> <div class="card bg-base-100 shadow-xl">
<p class="card-header-title">Arbeit</p> <div class="card-body">
</header> <div class="flex justify-between items-center mb-6">
<h1 class="text-3xl font-bold">Klassenarbeit-Bewertungssystem</h1>
<ThemeSwitcher />
</div>
<!-- <h1 class="text-3xl font-bold text-center mb-6"> -->
<!-- Klassenarbeit-Bewertungssystem -->
<!-- </h1> -->
<div class="card-content"> <div class="divider"></div>
<div class="content"> <h3 class="text-2xl font-bold mb-4">Bewertungen</h3>
<h1 class="title">Bewertungen</h1>
{#if maxPunkte.hvMax === 0} <MaxPunkteForm
<form on:submit|preventDefault={handleSetMaxPunkte}> bind:hvMax
<div class="field is-grouped"> bind:lvMax
<div class="control"> bind:hvGewichtung
<input class="input" type="number" bind:value={hvMax} placeholder="HV-Max-Punkte" required> bind:lvGewichtung
</div> onSubmit={handleSetMaxPunkte}
<div class="control"> />
<input class="input" type="number" bind:value={hvGewichtung} placeholder="HV-Gewichtung in %" required>
</div>
<div class="control">
<input class="input" type="number" bind:value={lvMax} placeholder="LV-Max-Punkte" required>
</div>
<div class="control">
<input class="input" type="number" bind:value={lvGewichtung} placeholder="LV-Gewichtung in %" required>
</div>
<div class="control">
<button type="submit" class="button is-primary">Setzen</button>
</div>
</div>
</form>
{/if}
<form on:submit|preventDefault={handleAddBewertung}>
<div class="field is-grouped">
<div class="control">
<input class="input" type="text" bind:value={vorname} placeholder="Vorname" required>
</div>
<div class="control">
<input class="input" type="text" bind:value={nachname} placeholder="Nachname" required>
</div>
<div class="control">
<input class="input" type="number" bind:value={hvPunkte} placeholder="HV-Punkte" required>
</div>
<div class="control">
<input class="input" type="number" bind:value={lvPunkte} placeholder="LV-Punkte" required>
</div>
<div class="control">
<button type="submit" class="button is-primary">Hinzufügen</button>
</div>
</div>
</form>
<div class="table-container"> <BewertungForm
<table class="table is-hoverable is-fullwidth"> bind:vorname
<thead> bind:nachname
<tr> bind:hvPunkte
<th>Gewertet</th> bind:lvPunkte
<th>Vorname</th> onSubmit={handleAddBewertung}
<th>Nachname</th> disabled={!isMaxPunkteValid}
<th>HV-Punkte</th> />
<th>HV-Prozent</th>
<th>HV-Note</th>
<th>LV-Punkte</th>
<th>LV-Prozent</th>
<th>LV-Note</th>
<th>Gesamt-Prozent</th>
<th>Gesamt-Note</th>
</tr>
</thead>
<tbody>
{#each bewertungen as bewertung}
<tr>
<td><input type="checkbox" checked={bewertung.gewertet} on:change={() => handleToggleWertung(bewertung.id)}></td>
<td>{bewertung.vorname}</td>
<td>{bewertung.nachname}</td>
<td>{bewertung.hvPunkte.toFixed(2)}</td>
<td>{bewertung.hvProzent.toFixed(2)}</td>
<td>{bewertung.hvNote}</td>
<td>{bewertung.lvPunkte.toFixed(2)}</td>
<td>{bewertung.lvProzent.toFixed(2)}</td>
<td>{bewertung.lvNote}</td>
<td>{bewertung.gesamtProzent.toFixed(2)}</td>
<td>{bewertung.gesamtNote}</td>
</tr>
{/each}
</tbody>
</table>
</div>
<h2 class="title">Notenspiegel</h2>
<table class="table is-hoverable is-fullwidth">
<thead>
<tr>
{#each [1,2,3,4,5,6] as note}
<th>{note}</th>
{/each}
</tr>
</thead>
<tbody>
<tr>
{#each [1,2,3,4,5,6] as note}
{#if notenspiegel[note]}
<td>{notenspiegel[note]}</td>
{:else}
<td>0</td>
{/if}
{/each}
</tr>
</tbody>
</table>
<div class="field is-grouped"> <BewertungenTable {bewertungen} onToggleWertung={handleToggleWertung} />
<div class="control">
<input type="text" class="input" bind:value={exportPath} readonly>
</div>
<div class="control">
<button class="button is-info" on:click={selectExportPath}>Pfad auswählen</button>
</div>
<div class="control">
<button class="button is-info" on:click={handleExport}>Exportieren</button>
</div>
</div>
</div> <!-- content -->
</div> <!-- card-content -->
</div> <!-- card --> <Notenspiegel {notenspiegel} />
</div> <!-- container -->
<ExportSection
bind:exportPath
onSelectPath={selectExportPath}
onExport={handleExport}
/>
</div>
</div>
</div>

View file

@ -0,0 +1,45 @@
<script>
export let vorname;
export let nachname;
export let hvPunkte;
export let lvPunkte;
export let onSubmit;
export let disabled = false;
</script>
<form on:submit|preventDefault={onSubmit} class="mb-4">
<div class="flex flex-wrap gap-2">
<input
class="input input-bordered"
type="text"
bind:value={vorname}
placeholder="Vorname"
required
/>
<input
class="input input-bordered"
type="text"
bind:value={nachname}
placeholder="Nachname"
required
/>
<input
class="input input-bordered"
type="number"
bind:value={hvPunkte}
placeholder="HV-Punkte"
required
/>
<input
class="input input-bordered"
type="number"
bind:value={lvPunkte}
placeholder="LV-Punkte"
required
/>
<button type="submit" class="btn btn-primary" {disabled}>
Hinzufügen
</button>
</div>
</form>

View file

@ -0,0 +1,25 @@
<script>
export let bewertung;
export let onToggleWertung;
</script>
<tr>
<td>
<input
type="checkbox"
class="toggle"
checked={bewertung.gewertet}
on:change={() => onToggleWertung(bewertung.id)}
/>
</td>
<td>{bewertung.vorname}</td>
<td>{bewertung.nachname}</td>
<td>{bewertung.hvPunkte.toFixed(2)}</td>
<td>{bewertung.hvProzent.toFixed(2)}</td>
<td>{bewertung.hvNote}</td>
<td>{bewertung.lvPunkte.toFixed(2)}</td>
<td>{bewertung.lvProzent.toFixed(2)}</td>
<td>{bewertung.lvNote}</td>
<td>{bewertung.gesamtProzent.toFixed(2)}</td>
<td>{bewertung.gesamtNote}</td>
</tr>

View file

@ -0,0 +1,39 @@
<script>
import BewertungRow from "./BewertungRow.svelte";
export let bewertungen;
export let onToggleWertung;
</script>
<div class="max-h-[500px] overflow-y-auto overflow-x-auto">
<table class="table w-full">
<!-- <div class="overflow-x-auto overflow-y auto"> -->
<!-- <table class="table w-full table-container"> -->
<thead>
<tr>
<th>Gewertet</th>
<th>Vorname</th>
<th>Nachname</th>
<th>HV-Punkte</th>
<th>HV-Prozent</th>
<th>HV-Note</th>
<th>LV-Punkte</th>
<th>LV-Prozent</th>
<th>LV-Note</th>
<th>Gesamt-Prozent</th>
<th>Gesamt-Note</th>
</tr>
</thead>
<tbody>
{#each bewertungen as bewertung (bewertung.id)}
<BewertungRow {bewertung} {onToggleWertung} />
{/each}
</tbody>
</table>
</div>
<!-- <style> -->
<!-- .table-container { -->
<!-- max-height: 400px; -->
<!-- overflow-y: auto; -->
<!-- } -->
<!-- </style> -->

View file

@ -0,0 +1,16 @@
<script>
export let exportPath;
export let onSelectPath;
export let onExport;
</script>
<div class="flex flex-wrap gap-2 mt-4">
<input
type="text"
class="input input-bordered flex-grow"
bind:value={exportPath}
readonly
/>
<button class="btn btn-info" on:click={onSelectPath}>Pfad auswählen</button>
<button class="btn btn-info" on:click={onExport}>Exportieren</button>
</div>

View file

@ -0,0 +1,52 @@
<script>
export let hvMax;
export let lvMax;
export let hvGewichtung;
export let lvGewichtung;
export let onSubmit;
</script>
<form on:change|preventDefault={onSubmit} class="mb-4">
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div class="form-control">
<label class="label" for="hvMax">HV-Max-Punkte</label>
<input
id="hvMax"
class="input input-bordered"
type="number"
bind:value={hvMax}
required
/>
</div>
<div class="form-control">
<label class="label" for="hvGewichtung">HV-Gewichtung in %</label>
<input
id="hvGewichtung"
class="input input-bordered"
type="number"
bind:value={hvGewichtung}
required
/>
</div>
<div class="form-control">
<label class="label" for="lvMax">LV-Max-Punkte</label>
<input
id="lvMax"
class="input input-bordered"
type="number"
bind:value={lvMax}
required
/>
</div>
<div class="form-control">
<label class="label" for="lvGewichtung">LV-Gewichtung in %</label>
<input
id="lvGewichtung"
class="input input-bordered"
type="number"
bind:value={lvGewichtung}
required
/>
</div>
</div>
</form>

View file

@ -0,0 +1,23 @@
<script>
export let notenspiegel;
</script>
<h3 class="text-2xl font-bold mt-8 mb-4">Notenspiegel</h3>
<div class="overflow-x-auto">
<table class="table w-full">
<thead>
<tr>
{#each [1, 2, 3, 4, 5, 6] as note}
<th>{note}</th>
{/each}
</tr>
</thead>
<tbody>
<tr>
{#each [1, 2, 3, 4, 5, 6] as note}
<td>{notenspiegel[note] || 0}</td>
{/each}
</tr>
</tbody>
</table>
</div>

View file

@ -0,0 +1,48 @@
<!-- <script> -->
<!-- import { onMount } from "svelte"; -->
<!---->
<!-- let currentTheme = "macchiato"; -->
<!---->
<!-- function toggleTheme() { -->
<!-- currentTheme = currentTheme === "macchiato" ? "latte" : "macchiato"; -->
<!-- document.getElementById("app").setAttribute("data-theme", currentTheme); -->
<!-- } -->
<!---->
<!-- onMount(() => { -->
<!-- currentTheme = document.getElementById("app").getAttribute("data-theme"); -->
<!-- }); -->
<!-- </script> -->
<!---->
<!-- <button on:click={toggleTheme} class="btn btn-primary"> -->
<!-- Switch to {currentTheme === "macchiato" ? "Latte" : "Macchiato"} -->
<!-- </button> -->
<!---->
<script>
import { onMount } from "svelte";
let currentTheme;
function toggleTheme() {
currentTheme = currentTheme === "macchiato" ? "latte" : "macchiato";
document.getElementById("app").setAttribute("data-theme", currentTheme);
localStorage.setItem("theme", currentTheme);
}
onMount(() => {
currentTheme = localStorage.getItem("theme") || "macchiato";
document.getElementById("app").setAttribute("data-theme", currentTheme);
});
$: isChecked = currentTheme === "macchiato";
</script>
<label class="swap swap-rotate">
<input
type="checkbox"
class="toggle theme-controller"
bind:checked={isChecked}
on:change={toggleTheme}
/>
<span class="swap-on">🌞</span>
<span class="swap-off">🌙</span>
</label>

259
main.go
View file

@ -2,213 +2,18 @@
package main package main
import ( import (
"context"
"embed" "embed"
"fmt" "fmt"
"strconv"
"github.com/jung-kurt/gofpdf"
"github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/assetserver"
"github.com/wailsapp/wails/v2/pkg/runtime" "github.com/wailsapp/wails/v2/pkg/options/linux"
"github.com/wailsapp/wails/v2/pkg/options/mac"
"github.com/wailsapp/wails/v2/pkg/options/windows"
) )
type Bewertung struct {
Vorname string `json:"vorname"`
Nachname string `json:"nachname"`
ID int `json:"id"`
HvPunkte float64 `json:"hvPunkte"`
HvProzent float64 `json:"hvProzent"`
HvNote int `json:"hvNote"`
LvPunkte float64 `json:"lvPunkte"`
LvProzent float64 `json:"lvProzent"`
LvNote int `json:"lvNote"`
GesamtProzent float64 `json:"gesamtProzent"`
GesamtNote int `json:"gesamtNote"`
Gewertet bool `json:"gewertet"`
}
type MaxPunkte struct {
HvMax float64 `json:"hvMax"`
LvMax float64 `json:"lvMax"`
HvGewichtung float64 `json:"hvGewichtung"`
LvGewichtung float64 `json:"lvGewichtung"`
}
type App struct {
ctx context.Context
bewertungen []Bewertung
maxPunkte MaxPunkte
}
func NewApp() *App {
return &App{
bewertungen: make([]Bewertung, 0),
maxPunkte: MaxPunkte{
HvMax: 0.00,
HvGewichtung: 0.00,
LvMax: 0.00,
LvGewichtung: 0.00,
},
}
}
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}
func (a *App) GetBewertungen() []Bewertung {
return a.bewertungen
}
func (a *App) GetMaxPunkte() MaxPunkte {
return a.maxPunkte
}
func (a *App) ToggleWertung(id int) Bewertung {
var updatedBewertung Bewertung
for i, bewertung := range a.bewertungen {
if bewertung.ID == id {
a.bewertungen[i].Gewertet = !bewertung.Gewertet
updatedBewertung = a.bewertungen[i]
break
}
}
return updatedBewertung
}
func (a *App) AddBewertung(vorname, nachname string, hvPunkte, lvPunkte float64) bool {
if !a.validateName(vorname, nachname) {
return false
}
hvProzent := 100.00 / a.maxPunkte.HvMax * hvPunkte
lvProzent := 100.00 / a.maxPunkte.LvMax * lvPunkte
hvNote := setNote(hvProzent)
lvNote := setNote(lvProzent)
gesamtProzent := hvProzent*a.maxPunkte.HvGewichtung/100 + lvProzent*a.maxPunkte.LvGewichtung/100
gesamtNote := setNote(gesamtProzent)
bewertung := Bewertung{
ID: len(a.bewertungen) + 1,
Vorname: vorname,
Nachname: nachname,
HvPunkte: hvPunkte,
HvProzent: hvProzent,
HvNote: int(hvNote),
LvPunkte: lvPunkte,
LvProzent: lvProzent,
LvNote: int(lvNote),
GesamtProzent: gesamtProzent,
GesamtNote: int(gesamtNote),
Gewertet: true,
}
a.bewertungen = append(a.bewertungen, bewertung)
return true
}
func (a *App) SetMaxPunkte(hvMax, lvMax, hvGewichtung, lvGewichtung float64) bool {
if !checkGewichtung(lvGewichtung, hvGewichtung) {
return false
}
a.maxPunkte = MaxPunkte{
HvMax: hvMax,
LvMax: lvMax,
HvGewichtung: hvGewichtung,
LvGewichtung: lvGewichtung,
}
return true
}
func (a *App) ExportBewertungen(path string) error {
pdf := gofpdf.New("P", "mm", "A4", "")
pdf.AddPage()
pdf.SetFont("Arial", "B", 12)
pdf.CellFormat(27, 10, "Vorname", "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, "Nachname", "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, "HV-Punkte", "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, "HV-Note", "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, "LV-Punkte", "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, "LV-Note", "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, "Gesamtnote", "1", 0, "", false, 0, "")
pdf.Ln(-1)
pdf.SetFont("Arial", "", 11)
for _, bewertung := range a.bewertungen {
pdf.CellFormat(27, 10, bewertung.Vorname, "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, bewertung.Nachname, "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, strconv.FormatFloat(bewertung.HvPunkte, 'f', 2, 64), "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, strconv.FormatInt(int64(bewertung.HvNote), 10), "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, strconv.FormatFloat(bewertung.LvPunkte, 'f', 2, 64), "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, strconv.FormatInt(int64(bewertung.LvNote), 10), "1", 0, "", false, 0, "")
pdf.CellFormat(27, 10, strconv.FormatInt(int64(bewertung.GesamtNote), 10), "1", 0, "", false, 0, "")
pdf.Ln(-1)
}
err := pdf.OutputFileAndClose(path)
if err != nil {
fmt.Println("Fehler beim Exportieren der Bewertungen:", err)
return err
}
runtime.EventsEmit(a.ctx, "export-complete")
return nil
}
func (a *App) GetNotenspiegel() map[int]int {
notenspiegel := make(map[int]int)
for _, bewertung := range a.bewertungen {
notenspiegel[bewertung.GesamtNote]++
}
return notenspiegel
}
func (a *App) validateName(vorname, nachname string) bool {
for _, bewertung := range a.bewertungen {
if bewertung.Nachname == nachname && bewertung.Vorname == vorname {
return false
}
}
return true
}
func setNote(prozent float64) float64 {
switch {
case prozent <= 22:
return 6.00
case prozent <= 49:
return 5.00
case prozent <= 64:
return 4.00
case prozent <= 79:
return 3.00
case prozent <= 94:
return 2.00
default:
return 1.00
}
}
func checkGewichtung(lv, hv float64) bool {
sum := hv/100 + lv/100
return sum == 1
}
func (a *App) OpenSaveDialog() (string, error) {
return runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{
DefaultFilename: "bewertungen.pdf",
Filters: []runtime.FileFilter{
{DisplayName: "PDF Files (*.pdf)", Pattern: "*.pdf"},
},
})
}
//go:embed all:frontend/dist //go:embed all:frontend/dist
var assets embed.FS var assets embed.FS
@ -216,19 +21,65 @@ func main() {
app := NewApp() app := NewApp()
err := wails.Run(&options.App{ err := wails.Run(&options.App{
Title: "Bewertungen", Title: "Notenverwaltung",
Width: 1024, Width: 1400,
Height: 768, Height: 1000,
MinWidth: 1024, MinWidth: 1200,
MinHeight: 768, MinHeight: 1000,
DisableResize: false,
Fullscreen: false,
WindowStartState: options.Maximised,
StartHidden: false,
HideWindowOnClose: false,
AssetServer: &assetserver.Options{ AssetServer: &assetserver.Options{
Assets: assets, Assets: assets,
}, },
Logger: nil,
LogLevel: logger.DEBUG,
LogLevelProduction: logger.ERROR,
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup, OnStartup: app.startup,
Bind: []interface{}{ Bind: []interface{}{
app, app,
}, },
EnableDefaultContextMenu: false,
EnableFraudulentWebsiteDetection: false,
Windows: &windows.Options{
WebviewIsTransparent: false,
WindowIsTranslucent: false,
BackdropType: windows.Mica,
DisablePinchZoom: false,
DisableWindowIcon: false,
DisableFramelessWindowDecorations: false,
WebviewUserDataPath: "",
WebviewBrowserPath: "",
Theme: windows.SystemDefault,
},
Mac: &mac.Options{
TitleBar: &mac.TitleBar{
TitlebarAppearsTransparent: true,
HideTitle: false,
HideTitleBar: false,
FullSizeContent: false,
UseToolbar: false,
HideToolbarSeparator: true,
},
Appearance: mac.NSAppearanceNameDarkAqua,
WebviewIsTransparent: true,
WindowIsTranslucent: false,
About: &mac.AboutInfo{
Title: "Notenverwaltung",
Message: "© 2024 Pata1704",
},
},
Linux: &linux.Options{
WindowIsTranslucent: false,
WebviewGpuPolicy: linux.WebviewGpuPolicyAlways,
ProgramName: "Notenverwaltung",
},
Debug: options.Debug{
OpenInspectorOnStartup: false,
},
}) })
if err != nil { if err != nil {
fmt.Println("Error:", err) fmt.Println("Error:", err)

23
models.go Normal file
View file

@ -0,0 +1,23 @@
package main
type Bewertung struct {
Vorname string `json:"vorname"`
Nachname string `json:"nachname"`
ID int `json:"id"`
HvPunkte float64 `json:"hvPunkte"`
HvProzent float64 `json:"hvProzent"`
HvNote int `json:"hvNote"`
LvPunkte float64 `json:"lvPunkte"`
LvProzent float64 `json:"lvProzent"`
LvNote int `json:"lvNote"`
GesamtProzent float64 `json:"gesamtProzent"`
GesamtNote int `json:"gesamtNote"`
Gewertet bool `json:"gewertet"`
}
type MaxPunkte struct {
HvMax float64 `json:"hvMax"`
LvMax float64 `json:"lvMax"`
HvGewichtung float64 `json:"hvGewichtung"`
LvGewichtung float64 `json:"lvGewichtung"`
}

171
package-lock.json generated Normal file
View file

@ -0,0 +1,171 @@
{
"name": "Noten",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"daisyui": "^4.12.14"
}
},
"node_modules/camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/css-selector-tokenizer": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz",
"integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
"fastparse": "^1.1.2"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"license": "MIT",
"bin": {
"cssesc": "bin/cssesc"
},
"engines": {
"node": ">=4"
}
},
"node_modules/culori": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz",
"integrity": "sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
"node_modules/daisyui": {
"version": "4.12.14",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.14.tgz",
"integrity": "sha512-hA27cdBasdwd4/iEjn+aidoCrRroDuo3G5W9NDKaVCJI437Mm/3eSL/2u7MkZ0pt8a+TrYF3aT2pFVemTS3how==",
"dev": true,
"license": "MIT",
"dependencies": {
"css-selector-tokenizer": "^0.8",
"culori": "^3",
"picocolors": "^1",
"postcss-js": "^4"
},
"engines": {
"node": ">=16.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/daisyui"
}
},
"node_modules/fastparse": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
"dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"peer": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/postcss": {
"version": "8.4.49",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-js": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"camelcase-css": "^2.0.1"
},
"engines": {
"node": "^12 || ^14 || >= 16"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
"peerDependencies": {
"postcss": "^8.4.21"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
}
}
}

5
package.json Normal file
View file

@ -0,0 +1,5 @@
{
"devDependencies": {
"daisyui": "^4.12.14"
}
}

23
utils.go Normal file
View file

@ -0,0 +1,23 @@
package main
func setNote(prozent float64) float64 {
switch {
case prozent <= 22:
return 6.00
case prozent <= 49:
return 5.00
case prozent <= 64:
return 4.00
case prozent <= 79:
return 3.00
case prozent <= 94:
return 2.00
default:
return 1.00
}
}
func checkGewichtung(lv, hv float64) bool {
sum := hv/100 + lv/100
return sum == 1
}