Browse Source

updated to use embedded templates; need to rethink structure/layout

cli-archive
nic 1 year ago
parent
commit
7f416aca38
  1. 5
      apps/web/main.go
  2. 6
      go.mod
  3. 12
      go.sum
  4. 23
      internal/handlers/web/admin.go
  5. 23
      internal/handlers/web/assets.go
  6. 27
      internal/handlers/web/companies.go
  7. 16
      internal/handlers/web/dashboard.go
  8. 38
      internal/handlers/web/invoices.go
  9. 5
      internal/handlers/web/login.go
  10. 87
      templates/layout.html
  11. 4
      templates/partials/admin.html
  12. 4
      templates/partials/assets.html
  13. 4
      templates/partials/companies.html
  14. 4
      templates/partials/jobs.html
  15. 55
      web_templates.go

5
apps/web/main.go

@ -4,6 +4,7 @@ import (
"log"
"net/http"
root "marmic/servicetrade-toolbox"
"marmic/servicetrade-toolbox/internal/handlers/web"
"marmic/servicetrade-toolbox/internal/middleware"
@ -11,6 +12,10 @@ import (
)
func main() {
err := root.InitializeWebTemplates() // Note the change here
if err != nil {
log.Fatalf("Failed to initialize web templates: %v", err)
}
r := mux.NewRouter()
// Serve static files

6
go.mod

@ -8,7 +8,7 @@ require (
)
require (
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/term v0.24.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/term v0.25.0 // indirect
)

12
go.sum

@ -2,9 +2,9 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3 h1:fO9A67/izFYFYky7l1pDP5Dr0BTCRkaQJUG6Jm5ehsk=
github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3/go.mod h1:Ey4uAp+LvIl+s5jRbOHLcZpUDnkjLBROl15fZLwPlTM=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=

23
internal/handlers/web/admin.go

@ -1,19 +1,28 @@
package web
import (
"html/template"
"log"
root "marmic/servicetrade-toolbox"
"marmic/servicetrade-toolbox/internal/api"
"net/http"
)
func AdminHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/admin.html"))
session, ok := r.Context().Value("session").(*api.Session)
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
tmpl := root.WebTemplates
data := map[string]interface{}{
"Title": "Admin",
"Title": "Admin Dashboard",
"Session": session,
}
if r.Header.Get("HX-Request") == "true" {
tmpl.ExecuteTemplate(w, "content", data)
} else {
tmpl.Execute(w, data)
if err := tmpl.ExecuteTemplate(w, "admin.html", data); err != nil {
log.Printf("Template execution error: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
}

23
internal/handlers/web/assets.go

@ -1,19 +1,28 @@
package web
import (
"html/template"
"log"
root "marmic/servicetrade-toolbox"
"marmic/servicetrade-toolbox/internal/api"
"net/http"
)
func AssetsHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/assets.html"))
session, ok := r.Context().Value("session").(*api.Session)
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
tmpl := root.WebTemplates
data := map[string]interface{}{
"Title": "Assets",
"Title": "Admin Dashboard",
"Session": session,
}
if r.Header.Get("HX-Request") == "true" {
tmpl.ExecuteTemplate(w, "content", data)
} else {
tmpl.Execute(w, data)
if err := tmpl.ExecuteTemplate(w, "assets.html", data); err != nil {
log.Printf("Template execution error: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
}

27
internal/handlers/web/companies.go

@ -1,19 +1,36 @@
package web
import (
"html/template"
"log"
root "marmic/servicetrade-toolbox"
"marmic/servicetrade-toolbox/internal/api"
"net/http"
)
func CompaniesHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/companies.html"))
session, ok := r.Context().Value("session").(*api.Session)
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
tmpl := root.WebTemplates
data := map[string]interface{}{
"Title": "Companies",
"Title": "Companies",
"Session": session,
"View": "company_content",
}
var err error
if r.Header.Get("HX-Request") == "true" {
tmpl.ExecuteTemplate(w, "content", data)
err = tmpl.ExecuteTemplate(w, "companies.html", data)
} else {
tmpl.Execute(w, data)
err = tmpl.Execute(w, data)
}
if err != nil {
log.Printf("Template execution error: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
}

16
internal/handlers/web/dashboard.go

@ -1,24 +1,14 @@
package web
import (
"html/template"
"log"
root "marmic/servicetrade-toolbox"
"net/http"
)
func DashboardHandler(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles(
"templates/layout.html",
"templates/dashboard.html",
"templates/partials/invoice_search.html",
"templates/partials/invoice_search_results.html",
)
if err != nil {
log.Printf("Template parsing error: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
var err error
tmpl := root.WebTemplates
data := struct{}{} // Empty struct as data
if r.Header.Get("HX-Request") == "true" {

38
internal/handlers/web/invoices.go

@ -4,9 +4,9 @@ import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"io"
"log"
root "marmic/servicetrade-toolbox"
"marmic/servicetrade-toolbox/internal/api"
"net/http"
"strings"
@ -23,7 +23,7 @@ var statusButtons = []StatusButton{
{"draft", "Draft Invoice", "success-button", "Are you sure you want to draft this invoice?"},
{"ok", "Ok Invoice", "success-button", "Are you sure you want to mark this invoice as OK?"},
{"fail", "Fail Invoice", "caution-button", "Are you sure you want to fail this invoice?"},
{"pending", "Pending Invoice", "caution-button", "Are you sure you want to mark this invoice as pending?"},
{"pending_accounting", "Pending Invoice", "caution-button", "Are you sure you want to mark this invoice as pending?"},
{"processed", "Process Invoice", "warning-button", "Are you sure you want to process this invoice?"},
{"void", "Void Invoice", "warning-button", "Are you sure you want to void this invoice?"},
}
@ -40,19 +40,28 @@ func InvoicesHandler(w http.ResponseWriter, r *http.Request) {
return
}
tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/invoices.html"))
tmpl := root.WebTemplates
data := map[string]interface{}{
"Title": "Invoices",
}
var err error
if r.Header.Get("HX-Request") == "true" {
tmpl.ExecuteTemplate(w, "content", data)
err = tmpl.ExecuteTemplate(w, "content", data)
} else {
tmpl.Execute(w, data)
err = tmpl.ExecuteTemplate(w, "layout.html", data)
}
if err != nil {
log.Printf("Template execution error: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
}
func handleInvoiceSearch(w http.ResponseWriter, r *http.Request, session *api.Session) {
tmpl := root.WebTemplates
searchTerm := strings.TrimSpace(r.URL.Query().Get("search"))
if searchTerm == "" {
@ -74,7 +83,6 @@ func handleInvoiceSearch(w http.ResponseWriter, r *http.Request, session *api.Se
if strings.Contains(err.Error(), "access forbidden") {
errorMsg = "You do not have permission to view this invoice."
}
tmpl := template.Must(template.ParseFiles("templates/partials/invoice_search_results.html"))
tmpl.ExecuteTemplate(w, "invoice_search_results", map[string]interface{}{
"Error": true,
"ErrorMsg": errorMsg,
@ -86,7 +94,6 @@ func handleInvoiceSearch(w http.ResponseWriter, r *http.Request, session *api.Se
if invoice == nil {
log.Printf("No invoice found for: %s", searchTerm)
w.WriteHeader(http.StatusOK)
tmpl := template.Must(template.ParseFiles("templates/partials/invoice_search_results.html"))
tmpl.ExecuteTemplate(w, "invoice_search_results", map[string]interface{}{
"NotFound": true,
"ErrorMsg": fmt.Sprintf("No invoice found for: %s", searchTerm),
@ -106,7 +113,6 @@ func handleInvoiceSearch(w http.ResponseWriter, r *http.Request, session *api.Se
invoice["buttons"] = getInvoiceStatusButtons(invoice["id"].(string), invoice["status"].(string))
}
tmpl := template.Must(template.ParseFiles("templates/partials/invoice_search_results.html"))
err = tmpl.ExecuteTemplate(w, "invoice_search_results", invoice)
if err != nil {
log.Printf("Error executing template: %v", err)
@ -137,6 +143,7 @@ func getInvoiceStatusButtons(invoiceID, currentStatus string) []map[string]strin
default:
// For all other statuses, show all buttons except the current status
for _, button := range statusButtons {
fmt.Printf("btn status: %s, curr status: %s", button.Status, currentStatus)
if button.Status != currentStatus {
buttons = append(buttons, map[string]string{
"Action": fmt.Sprintf("/%s-invoice/%s", button.Status, invoiceID),
@ -182,12 +189,12 @@ func UpdateInvoiceStatusHandler(w http.ResponseWriter, r *http.Request) {
// Validate the status
validStatuses := map[string]bool{
"draft": true,
"fail": true,
"ok": true,
"pending": true,
"processed": true,
"void": true,
"draft": true,
"fail": true,
"ok": true,
"pending_accounting": true,
"processed": true,
"void": true,
}
if !validStatuses[status] {
@ -231,8 +238,7 @@ func UpdateInvoiceStatusHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("Updated invoice after status change to %s: %+v", status, invoice)
// Render the updated invoice details
tmpl := template.Must(template.ParseFiles("templates/partials/invoice_search_results.html"))
err = tmpl.ExecuteTemplate(w, "invoice_search_results", invoice)
err = root.WebTemplates.ExecuteTemplate(w, "invoice_search_results", invoice)
if err != nil {
log.Printf("Error executing template: %v", err)
http.Error(w, "Error rendering template", http.StatusInternalServerError)

5
internal/handlers/web/login.go

@ -1,8 +1,8 @@
package web
import (
"html/template"
"log"
root "marmic/servicetrade-toolbox"
"marmic/servicetrade-toolbox/internal/api"
"marmic/servicetrade-toolbox/internal/middleware"
"net/http"
@ -11,8 +11,7 @@ import (
func LoginHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
tmpl := template.Must(template.ParseFiles("templates/login.html"))
tmpl.Execute(w, nil)
root.WebTemplates.ExecuteTemplate(w, "login.html", nil)
return
}

87
templates/layout.html

@ -1,46 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ServiceTrade Tools</title>
<script src="https://unpkg.com/htmx.org@1.9.6"></script>
<link rel="stylesheet" href="/static/css/styles.css" />
</head>
<body class="flex h-screen bg-gray-100">
<!-- Sidebar -->
<aside class="sidebar">
<h1 class="title">ServiceTrade Tools</h1>
<nav>
<ul>
<li><a href="/" hx-get="/" hx-target="#content">Dashboard</a></li>
<li><a href="/jobs" hx-get="/jobs" hx-target="#content">Jobs</a></li>
<li><a href="/assets" hx-get="/assets" hx-target="#content">Assets</a></li>
<li><a href="/companies" hx-get="/companies" hx-target="#content">Companies</a></li>
<li><a href="/contacts" hx-get="/contacts" hx-target="#content">Contacts</a></li>
<li><a href="/contracts" hx-get="/contracts" hx-target="#content">Contracts</a></li>
<li><a href="/generic" hx-get="/generic" hx-target="#content">Generic Tools</a></li>
<li><a href="/invoices" hx-get="/invoices" hx-target="#content">Invoices</a></li>
<li><a href="/locations" hx-get="/locations" hx-target="#content">Locations</a></li>
<li><a href="/notifications" hx-get="/notifications" hx-target="#content">Notifications</a></li>
<li><a href="/quotes" hx-get="/quotes" hx-target="#content">Quotes</a></li>
<li><a href="/services" hx-get="/services" hx-target="#content">Services</a></li>
<li><a href="/tags" hx-get="/tags" hx-target="#content">Tags</a></li>
<li><a href="/users" hx-get="/users" hx-target="#content">Users</a></li>
<li><a href="/admin" hx-get="/admin" hx-target="#content">Admin</a></li>
</ul>
</nav>
</aside>
<!-- Main content area -->
<main class="main-content">
<!-- Header with logout -->
<header class="header">
<button class="logout-btn" hx-post="/logout">Logout</button>
</header>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ServiceTrade Tools</title>
<script src="https://unpkg.com/htmx.org@1.9.6"></script>
<link rel="stylesheet" href="/static/css/styles.css" />
</head>
<!-- Dynamic content area -->
<div id="content" class="content">{{template "content" .}}</div>
</main>
</body>
</html>
<body class="flex h-screen bg-gray-100">
<!-- Sidebar -->
<aside class="sidebar">
<h1 class="title">ServiceTrade Tools</h1>
<nav>
<ul>
<li><a href="/" hx-get="/" hx-target="#content">Dashboard</a></li>
<li><a href="/jobs" hx-get="/jobs" hx-target="#content">Jobs</a></li>
<li><a href="/assets" hx-get="/assets" hx-target="#content">Assets</a></li>
<li><a href="/companies" hx-get="/companies" hx-target="#content">Companies</a></li>
<li><a href="/contacts" hx-get="/contacts" hx-target="#content">Contacts</a></li>
<li><a href="/contracts" hx-get="/contracts" hx-target="#content">Contracts</a></li>
<li><a href="/generic" hx-get="/generic" hx-target="#content">Generic Tools</a></li>
<li><a href="/invoices" hx-get="/invoices" hx-target="#content">Invoices</a></li>
<li><a href="/locations" hx-get="/locations" hx-target="#content">Locations</a></li>
<li><a href="/notifications" hx-get="/notifications" hx-target="#content">Notifications</a></li>
<li><a href="/quotes" hx-get="/quotes" hx-target="#content">Quotes</a></li>
<li><a href="/services" hx-get="/services" hx-target="#content">Services</a></li>
<li><a href="/tags" hx-get="/tags" hx-target="#content">Tags</a></li>
<li><a href="/users" hx-get="/users" hx-target="#content">Users</a></li>
<li><a href="/admin" hx-get="/admin" hx-target="#content">Admin</a></li>
</ul>
</nav>
</aside>
<!-- Main content area -->
<main class="main-content">
<!-- Header with logout -->
<header class="header">
<button class="logout-btn" hx-post="/logout">Logout</button>
</header>
<!-- Dynamic content area -->
<div id="content" class="content">{{template "content" .}}</div>
</main>
</body>
</html>

4
templates/partials/admin.html

@ -1,5 +1,5 @@
{{define "content"}}
{{define "admin"}}
<h2>Admin</h2>
<p>Manage your assets here.</p>
<!-- Add asset management content -->
{{end}}
{{end}}

4
templates/partials/assets.html

@ -1,5 +1,5 @@
{{define "content"}}
{{define "assets"}}
<h2>Assets</h2>
<p>Manage your assets here.</p>
<!-- Add asset management content -->
{{end}}
{{end}}

4
templates/partials/companies.html

@ -1,5 +1,5 @@
{{define "content"}}
{{define "companies"}}
<h2>Companies</h2>
<p>Manage your companies here.</p>
<!-- Add company management content -->
{{end}}
{{end}}

4
templates/partials/jobs.html

@ -1,4 +1,4 @@
{{define "content"}}
{{define "jobs"}}
<div class="submenu-container">
<h2 class="submenu-header">Jobs</h2>
<div class="submenu-grid">
@ -25,4 +25,4 @@
</div>
</div>
</div>
{{end}}
{{end}}

55
web_templates.go

@ -0,0 +1,55 @@
package root
import (
"embed"
"html/template"
"io/fs"
"log"
"path/filepath"
)
//go:embed templates
var webTemplateFS embed.FS
var WebTemplates *template.Template
// InitializeWebTemplates parses all HTML templates in the embedded filesystem
func InitializeWebTemplates() error {
var err error
WebTemplates, err = parseWebTemplates()
return err
}
func parseWebTemplates() (*template.Template, error) {
tmpl := template.New("")
err := fs.WalkDir(webTemplateFS, "templates", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
if filepath.Ext(path) != ".html" {
return nil
}
log.Printf("Parsing template: %s", path)
content, err := webTemplateFS.ReadFile(path)
if err != nil {
return err
}
_, err = tmpl.New(filepath.Base(path)).Parse(string(content))
return err
})
if err != nil {
return nil, err
}
return tmpl, nil
}
Loading…
Cancel
Save