diff --git a/controllers/bewertung.go b/controllers/bewertung.go new file mode 100644 index 0000000..8de7a2a --- /dev/null +++ b/controllers/bewertung.go @@ -0,0 +1,342 @@ +package controllers + +import ( + "echoTest/model" + "fmt" + "net/http" + "strconv" + + "github.com/chasefleming/elem-go" + "github.com/chasefleming/elem-go/attrs" + "github.com/chasefleming/elem-go/htmx" + "github.com/labstack/echo/v4" +) + +func UpdateGewertetRoute(bewertung model.Bewertung) elem.Node { + checkbox := elem.Input(attrs.Props{ + attrs.Type: "checkbox", + attrs.Checked: strconv.FormatBool(bewertung.Gewertet), + htmx.HXPost: "/toggle/" + strconv.Itoa(bewertung.ID), + htmx.HXTarget: "#bewertung-" + strconv.Itoa(bewertung.ID), + }) + return checkbox +} + +func RenderBewertungenRoute(c echo.Context) error { + return c.HTML(http.StatusOK, renderBewertungen(Bewertungen)) +} + +func ToggleWertungRoute(c echo.Context) error { + id, _ := strconv.Atoi(c.Param("id")) + var updatedBewertung model.Bewertung + for i, bewertung := range Bewertungen { + if bewertung.ID == id { + Bewertungen[i].Gewertet = !bewertung.Gewertet + updatedBewertung = Bewertungen[i] + break + } + } + return c.HTML(http.StatusOK, createBewertungNode(updatedBewertung).Render()) +} +func addBewertungRoute(c echo.Context) error { + new := parseBewertungen(c) + if new.Nachname != "" { + Bewertungen = append(Bewertungen, new) + } + return c.Redirect(http.StatusSeeOther, "/") +} +func parseBewertungen(c echo.Context) Bewertung { + newName := validateName(c) + vorname := c.FormValue("vorname") + if MaxPunkte.HvMax == 0.00 { + hvMax, _ := strconv.ParseFloat(c.FormValue("hv_max"), 64) + lvMax, _ := strconv.ParseFloat(c.FormValue("lv_max"), 64) + hvGewichtung, _ := strconv.ParseFloat(c.FormValue("hv_gewichtung"), 64) + lvGewichtung, _ := strconv.ParseFloat(c.FormValue("lv_gewichtung"), 64) + MaxPunkte.HvMax = hvMax + MaxPunkte.LvMax = lvMax + MaxPunkte.LvGewichtung = lvGewichtung + MaxPunkte.HvGewichtung = hvGewichtung + } + hvPunkte, _ := strconv.ParseFloat(c.FormValue("hv_punkte"), 64) + lvPunkte, _ := strconv.ParseFloat(c.FormValue("lv_punkte"), 64) + hvProzent := 100.00 / maxPunkte.HvMax * hvPunkte + lvProzent := 100.00 / maxPunkte.LvMax * lvPunkte + hvNote := setNote(hvProzent) + lvNote := setNote(lvProzent) + gesamtProzent := hvProzent*maxPunkte.HvGewichtung/100 + lvProzent*maxPunkte.LvGewichtung/100 + gesamtNote := setNote(gesamtProzent) + + // Create a new Bewertung struct + return model.Bewertung{ + ID: len(bewertungen) + 1, + Vorname: string(vorname), + Nachname: string(newName), + HvPunkte: hvPunkte, + HvProzent: hvProzent, + HvNote: int(hvNote), + LvPunkte: lvPunkte, + LvProzent: lvProzent, + LvNote: int(lvNote), + GesamtProzent: gesamtProzent, + GesamtNote: int(gesamtNote), + Gewertet: true, + } +} +func createBewertungNode(bewertung Bewertung) elem.Node { + checkbox := elem.Input(attrs.Props{ + attrs.Type: "checkbox", + attrs.Checked: strconv.FormatBool(bewertung.Gewertet), + htmx.HXPost: "/toggle/" + strconv.Itoa(bewertung.ID), + htmx.HXTarget: "#bewertung-" + strconv.Itoa(bewertung.ID), + htmx.HXSwap: "outerHTML", + }) + + return elem.Tr(attrs.Props{ + attrs.ID: "bewertung-" + strconv.Itoa(bewertung.ID), + }, + elem.Td(nil, checkbox), + elem.Td(nil, elem.Text(bewertung.Vorname)), + elem.Td(nil, elem.Text(bewertung.Nachname)), + elem.Td(nil, elem.Text(strconv.FormatFloat(bewertung.HvPunkte, 'f', 2, 64))), + elem.Td(nil, elem.Text(strconv.FormatFloat(bewertung.HvProzent, 'f', 2, 64))), + elem.Td(nil, elem.Text(strconv.Itoa(bewertung.HvNote))), + elem.Td(nil, elem.Text(strconv.FormatFloat(bewertung.LvPunkte, 'f', 2, 64))), + elem.Td(nil, elem.Text(strconv.FormatFloat(bewertung.LvProzent, 'f', 2, 64))), + elem.Td(nil, elem.Text(strconv.Itoa(bewertung.LvNote))), + elem.Td(nil, elem.Text(strconv.FormatFloat(bewertung.GesamtProzent, 'f', 2, 64))), + elem.Td(nil, elem.Text(strconv.Itoa(bewertung.GesamtNote))), + ) +} + +func renderBewertungen(bewertungen []Bewertung) string { + inputPunkte := elem.Div(nil) + if maxPunkte.HvGewichtung == 0.00 { + inputPunkte = elem.Div(attrs.Props{attrs.Class: "tile is-ancestor"}, + elem.Div(attrs.Props{attrs.Class: "tile field is-parent"}, + elem.Input(attrs.Props{ + attrs.Class: "input is-child", + attrs.Type: "text", + attrs.Name: "hv_max", + attrs.Placeholder: "HV-Max-Punkte", + }, + ), + ), + elem.Div(attrs.Props{attrs.Class: "tile field is-parent"}, + elem.Input(attrs.Props{ + attrs.Class: "input is-child", + attrs.Type: "text", + attrs.Name: "hv_gewichtung", + attrs.Placeholder: "HV-Gewichtung in %", + }, + ), + ), + elem.Div(attrs.Props{attrs.Class: "tile field is-parent"}, + elem.Input(attrs.Props{ + attrs.Class: "input is-child", + attrs.Type: "text", + attrs.Name: "lv_max", + attrs.Placeholder: "LV-Max-Punkte", + }, + ), + ), + elem.Div(attrs.Props{attrs.Class: "tile field is-parent"}, + elem.Input(attrs.Props{ + attrs.Class: "input is-child", + attrs.Type: "text", + attrs.Name: "lv_gewichtung", + attrs.Placeholder: "LV-Gewichtung in %", + }, + ), + ), + ) + } else { + inputPunkte = elem.Div(attrs.Props{attrs.Class: "tile is-ancestor"}, + elem.Div(attrs.Props{attrs.Class: "tile field is-parent"}, + elem.Input(attrs.Props{ + attrs.Class: "input is-child", + attrs.Type: "text", + attrs.Name: "hv_max", + attrs.Placeholder: "HV-Max-Punkte", + attrs.Value: fmt.Sprintf("%.2f", maxPunkte.HvMax), + }, + ), + ), + elem.Div(attrs.Props{attrs.Class: "tile field is-parent"}, + elem.Input(attrs.Props{ + attrs.Class: "input is-child", + attrs.Type: "text", + attrs.Name: "hv_gewichtung", + attrs.Placeholder: "HV-Gewichtung in %", + attrs.Value: fmt.Sprintf("%.2f", maxPunkte.HvGewichtung), + }, + ), + ), + elem.Div(attrs.Props{attrs.Class: "tile field is-parent"}, + elem.Input(attrs.Props{ + attrs.Class: "input is-child", + attrs.Type: "text", + attrs.Name: "lv_max", + attrs.Placeholder: "LV-Max-Punkte", + attrs.Value: fmt.Sprintf("%.2f", maxPunkte.LvMax), + }, + ), + ), + elem.Div(attrs.Props{attrs.Class: "tile field is-parent"}, + elem.Input(attrs.Props{ + attrs.Class: "input is-child", + attrs.Type: "text", + attrs.Name: "lv_gewichtung", + attrs.Placeholder: "LV-Gewichtung in %", + attrs.Value: fmt.Sprintf("%.2f", maxPunkte.LvGewichtung), + }, + ), + ), + ) + } + + headContent := elem.Head(nil, + elem.Meta(attrs.Props{attrs.Charset: "UTF-8", attrs.Name: "viewport", attrs.Content: "width=device-width, initial-scale=1.0"}), + elem.Script(attrs.Props{attrs.Src: "https://unpkg.com/htmx.org"}), + elem.Link(attrs.Props{attrs.Rel: "stylesheet", attrs.Href: "https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css"}), + ) + + headerContent := elem.Header(attrs.Props{ + attrs.Class: "navbar", + attrs.Role: "navigation", + attrs.AriaLabel: "main navigation", + }, + elem.Div(attrs.Props{ + attrs.ID: "navbarBasicExample", + attrs.Class: "navbar-menu", + }, + elem.Div(attrs.Props{ + attrs.Class: "navbar-start", + }, + elem.A(attrs.Props{ + attrs.Class: "navbar-item", + }, elem.Text("Home"), + ), + ), + elem.Div(attrs.Props{ + attrs.Class: "navbar-end", + }, + elem.Span(attrs.Props{ + attrs.Class: "navbar-item", + }, + elem.Button(attrs.Props{ + attrs.Class: "button is-primary", + htmx.HXTrigger: "click", + htmx.HXGet: "/end", + }, elem.Text("Beenden"), + ), + ), + ), + ), + ) + + bodyContent := elem.Div(attrs.Props{attrs.Class: "container is-widescreen"}, + elem.Div(attrs.Props{attrs.Class: "card tile is-vertical is-ancestor"}, + elem.Header(attrs.Props{attrs.Class: "card-header"}, + elem.P(attrs.Props{attrs.Class: "card-header-title"}, elem.Text("Englischarbeit"))), + elem.Div(attrs.Props{attrs.Class: "card-content"}, + elem.Div(attrs.Props{attrs.Class: "content tile is-parent is-vertical gap"}, + elem.H1(attrs.Props{attrs.Class: "tilte"}, elem.Text("Bewertungen")), + elem.Form(attrs.Props{attrs.Method: "post", attrs.Action: "/add"}, inputPunkte, + elem.Div(attrs.Props{attrs.Class: "tile is-ancestor"}, + elem.Div(attrs.Props{attrs.Class: "tile field is-parent"}, + elem.Input(attrs.Props{ + attrs.Type: "text", + attrs.Name: "vorname", + attrs.Class: "input is-child", + attrs.Placeholder: "Vorname", + }, + ), + ), + elem.Div(attrs.Props{attrs.Class: "tile field is-parent"}, + elem.Input(attrs.Props{ + attrs.Type: "text", + attrs.Name: "nachname", + attrs.Class: "input is-child", + attrs.Placeholder: "Nachname", + }, + ), + ), + elem.Div(attrs.Props{attrs.Class: "tile field is-parent"}, + elem.Input(attrs.Props{ + attrs.Type: "text", + attrs.Name: "hv_punkte", + attrs.Class: "input is-child", + attrs.Placeholder: "HV-Punkte", + }, + ), + ), + elem.Div(attrs.Props{attrs.Class: "tile field is-parent"}, + elem.Input(attrs.Props{ + attrs.Type: "text", + attrs.Name: "lv_punkte", + attrs.Class: "input is-child", + attrs.Placeholder: "LV-Punkte", + }, + ), + ), + elem.Div(attrs.Props{attrs.Class: "tile field is-parent"}, + elem.Button( + attrs.Props{ + attrs.Type: "submit", + attrs.Class: "button tile is-child", + }, + elem.Text("Add"), + ), + ), + ), + ), + elem.Div(attrs.Props{attrs.Class: "table-container"}, + elem.Table(attrs.Props{attrs.Class: "table is-hoverable"}, + elem.THead(nil, + elem.Tr(nil, + elem.Th(nil, elem.Text("Gewertet")), + elem.Th(nil, elem.Text("Vorname")), + elem.Th(nil, elem.Text("Nachname")), + elem.Th(nil, elem.Text("HV-Punkte")), + elem.Th(nil, elem.Text("HV-Prozent")), + elem.Th(nil, elem.Text("HV-Note")), + elem.Th(nil, elem.Text("LV-Punkte")), + elem.Th(nil, elem.Text("LV-Prozent")), + elem.Th(nil, elem.Text("LV-Note")), + elem.Th(nil, elem.Text("Gesamt-Prozent")), + elem.Th(nil, elem.Text("Gesamt-Note")), + ), + ), + elem.TBody(nil, + elem.TransformEach(bewertungen, createBewertungNode)...), + ), + ), + elem.Div(nil, + elem.Button(attrs.Props{ + htmx.HXTrigger: "click", + htmx.HXGet: "/export", + attrs.Class: "button", + }, + elem.Text("export"), + ), + ), + ), + ), + ), + ) + footerContent := elem.Footer(attrs.Props{ + attrs.Class: "footer", + }, + elem.Div(attrs.Props{ + attrs.Class: "content has-text-centered", + }, + elem.P(nil, elem.Text("© 2023 Alle Rechte vorbehalten.")), + ), + ) + + tbodyContent := elem.TBody(nil) + htmlContent := elem.Html(nil, elem.Raw(""), headContent, headerContent, bodyContent, tbodyContent, footerContent) + + return htmlContent.Render() +} diff --git a/go.mod b/go.mod index fa83268..2e9190b 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,18 @@ go 1.21.5 require ( github.com/chasefleming/elem-go v0.16.0 + github.com/jung-kurt/gofpdf v1.16.2 github.com/labstack/echo/v4 v4.11.4 + github.com/stretchr/testify v1.8.4 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect - github.com/jung-kurt/gofpdf v1.16.2 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect golang.org/x/crypto v0.17.0 // indirect @@ -20,4 +23,5 @@ require ( golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 93ba414..f13cfff 100644 --- a/go.sum +++ b/go.sum @@ -44,5 +44,7 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..a9c4a36 --- /dev/null +++ b/main_test.go @@ -0,0 +1,34 @@ +package main + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" +) + +func TestRenderBewertungenRoute(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + err := renderBewertungenRoute(c) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, rec.Code) +} + +func TestToggleWertungRoute(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/toggle/1", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + c.SetParamNames("id") + c.SetParamValues("1") + + err := toggleWertungRoute(c) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, rec.Code) +} diff --git a/model/bewertung.go b/model/bewertung.go new file mode 100644 index 0000000..6ad0e79 --- /dev/null +++ b/model/bewertung.go @@ -0,0 +1,24 @@ +package model + +// Todo model +type Bewertung struct { + Vorname string + Nachname string + ID int + HvPunkte float64 + HvProzent float64 + HvNote int + LvPunkte float64 + LvProzent float64 + LvNote int + GesamtProzent float64 + GesamtNote int + Gewertet bool +} + +type MaxPunkte struct { + HvMax float64 + LvMax float64 + HvGewichtung float64 + LvGewichtung float64 +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..e69de29