diff --git a/data/database.sql b/data/database.sql index c39d9a1..e5cb5b1 100755 --- a/data/database.sql +++ b/data/database.sql @@ -5,30 +5,30 @@ create table locations ( description text ); -insert into locations(id,parent,name,description) values(0,null,"Raum 1","Eingang / Kueche"); -insert into locations(id,parent,name,description) values(1,null,"Raum 2","Server / Chillout-Area"); -insert into locations(id,parent,name,description) values(2,null,"Raum 3","Hauptlager / Arbeitszimmer / Gemeinschaftsraum"); -insert into locations(id,parent,name,description) values(3,null,"Raum 4","Chemie / Nebenlager / 3D-Druck / Plotter"); -insert into locations(id,parent,name,description) values(4,null,"Raum 5","Maschinenraum / Werkstatt"); -insert into locations(id,parent,name,description) values(5,2,"A",null); -insert into locations(id,parent,name,description) values(6,2,"B",null); -insert into locations(id,parent,name,description) values(7,2,"C",null); -insert into locations(id,parent,name,description) values(8,2,"D",null); -insert into locations(id,parent,name,description) values(9,2,"E",null); -insert into locations(id,parent,name,description) values(10,2,"F",null); -insert into locations(id,parent,name,description) values(11,2,"G",null); +insert into locations(parent,name,description) values(null,"Raum 1","Eingang / Kueche"); +insert into locations(parent,name,description) values(null,"Raum 2","Server / Chillout-Area"); +insert into locations(parent,name,description) values(null,"Raum 3","Hauptlager / Arbeitszimmer / Gemeinschaftsraum"); +insert into locations(parent,name,description) values(null,"Raum 4","Chemie / Nebenlager / 3D-Druck / Plotter"); +insert into locations(parent,name,description) values(null,"Raum 5","Maschinenraum / Werkstatt"); +insert into locations(parent,name,description) values(2,"A",null); +insert into locations(parent,name,description) values(2,"B",null); +insert into locations(parent,name,description) values(2,"C",null); +insert into locations(parent,name,description) values(2,"D",null); +insert into locations(parent,name,description) values(2,"E",null); +insert into locations(parent,name,description) values(2,"F",null); +insert into locations(parent,name,description) values(2,"G",null); create table containers ( id integer primary key, name text ); -insert into containers(id,name) values(0,"Grosse Kiste"); -insert into containers(id,name) values(1,"Flache Kiste"); -insert into containers(id,name) values(2,"Komponentenschachtel"); -insert into containers(id,name) values(3,"Regal"); -insert into containers(id,name) values(4,"Tisch"); -insert into containers(id,name) values(5,"Schublade"); +insert into containers(name) values("Grosse Kiste"); +insert into containers(name) values("Flache Kiste"); +insert into containers(name) values("Komponentenschachtel"); +insert into containers(name) values("Regal"); +insert into containers(name) values("Tisch"); +insert into containers(name) values("Schublade"); create table parts ( id integer primary key, diff --git a/db.go b/db.go index 799d8fd..44e30f6 100755 --- a/db.go +++ b/db.go @@ -4,10 +4,23 @@ import ( "database/sql" "fmt" "os" + "strings" _ "github.com/mattn/go-sqlite3" ) +func ParseSearchString(search string) (out []any) { + for strings.Contains(search, " ") { + search = strings.ReplaceAll(search, " ", " ") + } + searchStrings := strings.Split(search, " ") + out = make([]any, len(searchStrings)) + for i, s := range searchStrings { + out[i] = "%" + s + "%" + } + return out +} + type Connection struct { DB *sql.DB Error error @@ -30,6 +43,73 @@ func (conn *Connection) InitDatabase() error { return err } +func (conn *Connection) SearchPart(search string) ([]*Part, error) { + parsed := ParseSearchString(search) + if len(parsed) == 0 || (len(parsed) == 1 && len(fmt.Sprintf("%v", parsed[0])) == 2) { + return []*Part{}, nil + } + + if conn.Error != nil { + return nil, conn.Error + } + + searchPattern := "" + + for i := 0; i < len(parsed); i++ { + searchPattern += "tags LIKE ?" + if i < len(parsed)-1 { + searchPattern += " OR " + } else { + searchPattern += " " + } + } + + rows, err := conn.DB.Query("select id, name, tags, location, container from parts where "+searchPattern, parsed...) + if err != nil { + return nil, err + } + + parts := make([]*Part, 0) + + for rows.Next() { + part := Part{} + + var locationId sql.NullInt64 + var containerId sql.NullInt64 + + err := rows.Scan(&part.Id, &part.Name, &part.Tags, &locationId, &containerId) + if err != nil { + return nil, err + } + + var location *Location + + if locationId.Valid { + location, err = conn.QueryLocation(locationId.Int64) + if err != nil { + return nil, err + } + } + + var container *Container + + if containerId.Valid { + container, err = conn.QueryContainer(containerId.Int64) + if err != nil { + return nil, err + } + } + + part.Location = *location + part.Container = *container + part.Connection = conn + + parts = append(parts, &part) + } + + return parts, nil +} + func (conn *Connection) QueryLocations() ([]*Location, error) { if conn.Error != nil { return nil, conn.Error @@ -49,6 +129,8 @@ func (conn *Connection) QueryLocations() ([]*Location, error) { return nil, err } + location.Connection = conn + locations = append(locations, &location) } @@ -69,6 +151,8 @@ func (conn *Connection) QueryLocation(id int64) (*Location, error) { return nil, err } + location.Connection = conn + return &location, nil } diff --git a/main.go b/main.go index 015b892..52356f8 100755 --- a/main.go +++ b/main.go @@ -2,11 +2,14 @@ package main import ( "log" + "math/rand/v2" "github.com/gofiber/fiber/v2" "github.com/gofiber/template/html/v2" ) +const figchProbability = 5 + func main() { engine := html.New("views", ".html") @@ -24,7 +27,10 @@ func main() { conn := Connect() app.Get("/", func(c *fiber.Ctx) error { + figch := rand.IntN(100) <= figchProbability + return c.Render("search", fiber.Map{ + "Figch": figch, "Title": "Suche", "Stylenames": NewStyleItemList("colors", "main", "search"), "NavItems": navItems, @@ -34,7 +40,10 @@ func main() { }) app.Get("/search", func(c *fiber.Ctx) error { + figch := rand.IntN(100) <= figchProbability + return c.Render("search", fiber.Map{ + "Figch": figch, "Title": "Suche", "Stylenames": NewStyleItemList("colors", "main", "search"), "NavItems": navItems, @@ -44,17 +53,59 @@ func main() { }) app.Post("/search", func(c *fiber.Ctx) error { + figch := rand.IntN(100) <= figchProbability + + search := c.FormValue("search", "") + + var parts []*Part + var err error + var table Table + var notification string + var resultCount int = -1 + + if IsEmpty(search) { + notification = "Sucheingabe darf nicht leer sein!" + search = "" + } else if len(search) < 3 { + notification = "Sucheingabe muss mehr als 2 Zeichen haben!" + search = "" + } else if !IsAlphaNumeric(search) { + notification = "Sucheingabe darf keine Sonderzeichen enthalten (außer Leerzeichen)" + search = "" + } else { + parts, err = conn.SearchPart(search) + if err != nil { + return err + } + table = ToTable[*Part](parts, TableColumns{ + "ID", + "Name", + "Tags", + "Ort", + "Behälter", + }) + resultCount = len(table.Rows) + } + return c.Render("search", fiber.Map{ + "Figch": figch, "Title": "Suche", "Stylenames": NewStyleItemList("colors", "main", "search"), "NavItems": navItems, "ActivePage": "/search", - "SearchResultCount": 0, + "SearchResultCount": resultCount, + "Columns": table.Collumns, + "Rows": table.Rows, + "Notification": notification, + "Search": search, }) }) app.Get("/admin", func(c *fiber.Ctx) error { + figch := rand.IntN(100) <= figchProbability + return c.Render("admin/tables", fiber.Map{ + "Figch": figch, "Title": "Verwaltung", "Stylenames": NewStyleItemList("colors", "main", "tables"), "NavItems": navItems, @@ -68,18 +119,20 @@ func main() { }) app.Get("/admin/locations/overview", func(c *fiber.Ctx) error { + figch := rand.IntN(100) <= figchProbability + locations, err := conn.QueryLocations() if err != nil { return err } table := ToTable[*Location](locations, TableColumns{ "ID", - "Übergeordneter Ort", - "Name", + "Ort", "Beschreibung", }) return c.Render("admin/overview", fiber.Map{ + "Figch": figch, "Title": "Verwaltung", "Stylenames": NewStyleItemList("colors", "main", "overview"), "NavItems": navItems, @@ -91,6 +144,8 @@ func main() { }) app.Get("/admin/containers/overview", func(c *fiber.Ctx) error { + figch := rand.IntN(100) <= figchProbability + containers, err := conn.QueryContainers() if err != nil { return err @@ -101,6 +156,7 @@ func main() { }) return c.Render("admin/overview", fiber.Map{ + "Figch": figch, "Title": "Verwaltung", "Stylenames": NewStyleItemList("colors", "main", "overview"), "NavItems": navItems, @@ -112,6 +168,8 @@ func main() { }) app.Get("/admin/parts/overview", func(c *fiber.Ctx) error { + figch := rand.IntN(100) <= figchProbability + parts, err := conn.QueryParts() if err != nil { return err @@ -125,6 +183,7 @@ func main() { }) return c.Render("admin/overview", fiber.Map{ + "Figch": figch, "Title": "Verwaltung", "Stylenames": NewStyleItemList("colors", "main", "overview"), "NavItems": navItems, diff --git a/misc.go b/misc.go index 92230c2..b5a7748 100755 --- a/misc.go +++ b/misc.go @@ -1,5 +1,9 @@ package main +import ( + "unicode" +) + func ToTable[T DatabaseType](rows []T, columns TableColumns) Table { tableRows := make([]TableRow, len(rows)) @@ -12,3 +16,24 @@ func ToTable[T DatabaseType](rows []T, columns TableColumns) Table { tableRows, } } + +func IsAlphaNumeric(s string) bool { + for _, r := range s { + if !(unicode.IsLetter(r) || unicode.IsNumber(r) || unicode.IsSpace(r)) { + return false + } + } + return true +} + +func IsEmpty(s string) bool { + if len(s) == 0 { + return true + } + for _, r := range s { + if !unicode.IsSpace(r) { + return false + } + } + return true +} diff --git a/static/css/overview.css b/static/css/overview.css index 7e66fdd..30deab6 100755 --- a/static/css/overview.css +++ b/static/css/overview.css @@ -1,26 +1,62 @@ main { display: flex; justify-content: center; - align-items: center; + align-items: start; padding-top: 2em; padding-bottom: 2em; } +a.table-header-btn, a.table-header-btn:visited { + color: var(--link); + border: 2px solid var(--link); + padding: .5em; + border-radius: 4px; +} + table { border-collapse: collapse; width: 1200px; max-width: 100%; } -table :is(th, td) { +table tr:first-child { + border: none; + background-color: transparent !important; +} + +table tr:nth-child(2n + 1) { + background-color: var(--bg-border); +} + +table tr { border: 2px solid var(--bg-border); - padding: 1em; + border-top: none; + border-bottom: none; +} + +table tr:nth-child(2) { + background-color: var(--fg); + color: var(--bg-light); + border: 2px solid var(--fg); + border-bottom: none; + font-size: 1.2em; +} + +table tr:last-child { + border: 2px solid var(--bg-border); + border-top: none; +} + +table tr :is(th, td) { + padding: 10px; + text-align: left; } table tr td:last-child > div.action-container { display: flex; align-items: baseline; - justify-content: space-around; + justify-content: start; + gap: .1em; } table tr td:last-child form { @@ -35,4 +71,5 @@ table tr td:last-child form button { border: none; cursor: pointer; color: var(--fg); + font-size: 1em; } \ No newline at end of file diff --git a/static/css/search.css b/static/css/search.css index 7f0efaa..4e557da 100755 --- a/static/css/search.css +++ b/static/css/search.css @@ -6,12 +6,12 @@ main { gap: 50px; } -main > form { +main form { display: flex; margin-bottom: 50px; } -main > form > input { +main form > input { width: 420px; height: 50px; border-radius: 0px; @@ -25,11 +25,11 @@ main > form > input { border-right: 2px solid var(--bg-border); } -main > form > input:focus { +main form > input:focus { outline: none; } -main > form > button { +main form > button { width: 50px; height: 50px; border: none; @@ -51,4 +51,14 @@ main > table tr :is(td, th) { border: 2px solid var(--bg-border); padding: 5px; text-align: center; +} + +main div.form-with-notification { + width: 472px; +} + +main div.form-with-notification > p.notification { + text-align: center; + color: var(--accent); + font-weight: bold; } \ No newline at end of file diff --git a/types.go b/types.go index 828c36b..448e619 100755 --- a/types.go +++ b/types.go @@ -41,35 +41,30 @@ type Location struct { Parent sql.NullInt64 Name sql.NullString Description sql.NullString + Connection *Connection } func (l *Location) ToTableRow() TableRow { - columns := make(TableColumns, 4) + columns := make(TableColumns, 3) tr := TableRow{} + var path string + if l.Id.Valid { columns[0] = fmt.Sprintf("%d", l.Id.Int64) tr.Id = int(l.Id.Int64) + path, _ = l.Connection.QueryLocationFullPath(l.Id.Int64) } else { columns[0] = "NULL" tr.Id = -1 } - if l.Parent.Valid { - columns[1] = fmt.Sprintf("%d", l.Parent.Int64) - } else { - columns[1] = "NULL" - } - if l.Name.Valid { - columns[2] = l.Name.String + columns[1] = path + if l.Description.Valid { + columns[2] = l.Description.String } else { columns[2] = "NULL" } - if l.Description.Valid { - columns[3] = l.Description.String - } else { - columns[3] = "NULL" - } tr.Columns = columns diff --git a/views/admin/overview.html b/views/admin/overview.html index 34477fd..8471646 100755 --- a/views/admin/overview.html +++ b/views/admin/overview.html @@ -1,13 +1,19 @@ {{template "partials/base-top" .}} +{{ $Table := .Table }} + + + + {{ range .Columns }} {{ end }} - {{ $Table := .Table }} {{ range .Rows }} {{ range .Columns }} diff --git a/views/partials/base-top.html b/views/partials/base-top.html index b9f6036..fd9b33e 100755 --- a/views/partials/base-top.html +++ b/views/partials/base-top.html @@ -3,14 +3,14 @@ - fisch - {{ .Title }} + {{ if .Figch }}figch{{ else }}fisch{{ end }} - {{ .Title }} {{ range .Stylenames }} {{ end }}
-

fisch

+

{{ if .Figch }}figch{{ else }}fisch{{ end }}

+ Hinzufügen +
{{ . }}Aktionen
- - - - - - - - - - + + {{ range .Columns }} + + {{ end }} + + {{ range .Rows }} + + {{ range .Columns }} + + {{ end }} + + {{ end }}
OrtKistenbezeichnungMöglicher Inhalt
AAA
{{ . }}
{{ . }}
{{ else }}