[Add search function]
This commit is contained in:
parent
d8d6820428
commit
b33dc14db9
10 changed files with 288 additions and 56 deletions
|
@ -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,
|
||||
|
|
84
db.go
84
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
|
||||
}
|
||||
|
||||
|
|
65
main.go
65
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,
|
||||
|
|
25
misc.go
25
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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
21
types.go
21
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
|
||||
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
{{template "partials/base-top" .}}
|
||||
|
||||
{{ $Table := .Table }}
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>
|
||||
<a class="table-header-btn" href="/admin/{{ $Table }}/add">Hinzufügen</a>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
{{ range .Columns }}
|
||||
<th>{{ . }}</th>
|
||||
{{ end }}
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
{{ $Table := .Table }}
|
||||
{{ range .Rows }}
|
||||
<tr>
|
||||
{{ range .Columns }}
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>fisch - {{ .Title }}</title>
|
||||
<title>{{ if .Figch }}figch{{ else }}fisch{{ end }} - {{ .Title }}</title>
|
||||
{{ range .Stylenames }}
|
||||
<link rel="stylesheet" href="/css/{{ . }}.css">
|
||||
{{ end }}
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>fisch</h1>
|
||||
<h1>{{ if .Figch }}figch{{ else }}fisch{{ end }}</h1>
|
||||
<nav>
|
||||
{{ range .NavItems }}
|
||||
<a href="{{ .Destination }}" {{ if eq .Destination $.ActivePage }} class="active" {{ end }}>{{ .Caption }}</a>
|
||||
|
|
|
@ -1,10 +1,24 @@
|
|||
{{template "partials/base-top" .}}
|
||||
|
||||
{{ if .Notification }}
|
||||
|
||||
<div class="form-with-notification">
|
||||
|
||||
<p class="notification">{{ .Notification }}</p>
|
||||
|
||||
{{ end }}
|
||||
|
||||
<form action="/search" method="post">
|
||||
<input type="text" name="search" placeholder="Suche . . .">
|
||||
<input type="text" name="search" placeholder="Suche . . ." {{ if .Search }} value="{{ .Search }}" {{ end }}>
|
||||
<button type="submit">🔍</button>
|
||||
</form>
|
||||
|
||||
{{ if .Notification }}
|
||||
|
||||
</divdiv>
|
||||
|
||||
{{ end }}
|
||||
|
||||
|
||||
{{ if eq .SearchResultCount -1 }}
|
||||
|
||||
|
@ -13,16 +27,18 @@
|
|||
{{ if gt .SearchResultCount 0 }}
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Ort</th>
|
||||
<th>Kistenbezeichnung</th>
|
||||
<th>Möglicher Inhalt</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>A</td>
|
||||
<td>A</td>
|
||||
<td>A</td>
|
||||
</tr>
|
||||
<tr>
|
||||
{{ range .Columns }}
|
||||
<th>{{ . }}</th>
|
||||
{{ end }}
|
||||
</tr>
|
||||
{{ range .Rows }}
|
||||
<tr>
|
||||
{{ range .Columns }}
|
||||
<td>{{ . }}</td>
|
||||
{{ end }}
|
||||
</tr>
|
||||
{{ end }}
|
||||
</table>
|
||||
|
||||
{{ else }}
|
||||
|
|
Loading…
Add table
Reference in a new issue