Adding go basic layout
This commit is contained in:
parent
d804a88d15
commit
4cd67eaf6c
|
|
@ -1,50 +1,23 @@
|
|||
# Build stage
|
||||
FROM golang:1.23-alpine AS builder
|
||||
# Dev Dockerfile for Go hot reload with Air and sqlc
|
||||
FROM golang:1.24.0
|
||||
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache git
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy go mod files
|
||||
COPY go-app/go.mod go-app/go.sum ./
|
||||
|
||||
# Download dependencies
|
||||
RUN go mod download
|
||||
# Install Air for hot reload
|
||||
RUN go install github.com/air-verse/air@latest
|
||||
# Install sqlc for SQL code generation
|
||||
RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
|
||||
|
||||
# Copy source code
|
||||
COPY go-app/ .
|
||||
|
||||
# Install sqlc (compatible with Go 1.23+)
|
||||
RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
|
||||
|
||||
# Generate sqlc code
|
||||
RUN sqlc generate
|
||||
|
||||
# Build the application
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server cmd/server/main.go
|
||||
|
||||
# Runtime stage
|
||||
FROM alpine:latest
|
||||
|
||||
# Install runtime dependencies
|
||||
RUN apk --no-cache add ca-certificates
|
||||
|
||||
WORKDIR /root/
|
||||
|
||||
# Copy the binary from builder
|
||||
COPY --from=builder /app/server .
|
||||
|
||||
# Copy templates and static files
|
||||
COPY go-app/templates ./templates
|
||||
COPY go-app/static ./static
|
||||
|
||||
# Copy .env file if needed
|
||||
# Copy Air config
|
||||
COPY go-app/.air.toml .air.toml
|
||||
COPY go-app/.env.example .env
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8080
|
||||
|
||||
# Run the application
|
||||
CMD ["./server"]
|
||||
CMD ["air", "-c", ".air.toml"]
|
||||
|
|
@ -2,6 +2,14 @@ server {
|
|||
server_name cmclocal;
|
||||
auth_basic_user_file /etc/nginx/userpasswd;
|
||||
auth_basic "Restricted";
|
||||
location /go/ {
|
||||
proxy_pass http://cmc-go:8080;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
location / {
|
||||
proxy_pass http://cmc-php:80;
|
||||
proxy_read_timeout 300s;
|
||||
|
|
|
|||
|
|
@ -75,25 +75,12 @@ services:
|
|||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./app/webroot/pdf:/root/webroot/pdf
|
||||
- ./go-app:/app
|
||||
- ./go-app/.air.toml:/root/.air.toml
|
||||
- ./go-app/.env.example:/root/.env
|
||||
networks:
|
||||
- cmc-network
|
||||
restart: unless-stopped
|
||||
develop:
|
||||
watch:
|
||||
- action: rebuild
|
||||
path: ./go-app
|
||||
ignore:
|
||||
- ./go-app/bin
|
||||
- ./go-app/.env
|
||||
- ./go-app/tmp
|
||||
- "**/.*" # Ignore hidden files
|
||||
- action: sync
|
||||
path: ./go-app/templates
|
||||
target: /app/templates
|
||||
- action: sync
|
||||
path: ./go-app/static
|
||||
target: /app/static
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
|
|
|
|||
25
go-app/.air.toml
Normal file
25
go-app/.air.toml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Air configuration for Go hot reload
|
||||
root = "./"
|
||||
cmd = ["air"]
|
||||
|
||||
[build]
|
||||
cmd = "go build -o server cmd/server/main.go"
|
||||
bin = "server"
|
||||
include = ["cmd", "internal", "go.mod", "go.sum"]
|
||||
exclude = ["bin", "tmp", ".env"]
|
||||
delay = 1000
|
||||
log = "stdout"
|
||||
kill_on_error = true
|
||||
|
||||
color = true
|
||||
|
||||
[[watch]]
|
||||
path = "templates"
|
||||
reload = true
|
||||
[[watch]]
|
||||
path = "static"
|
||||
reload = true
|
||||
[[watch]]
|
||||
path = "tmp"
|
||||
reload = false
|
||||
mkdir = true
|
||||
|
|
@ -52,39 +52,31 @@ func main() {
|
|||
log.Fatal("Failed to initialize templates:", err)
|
||||
}
|
||||
|
||||
// Create handlers
|
||||
customerHandler := handlers.NewCustomerHandler(queries)
|
||||
productHandler := handlers.NewProductHandler(queries)
|
||||
purchaseOrderHandler := handlers.NewPurchaseOrderHandler(queries)
|
||||
enquiryHandler := handlers.NewEnquiryHandler(queries)
|
||||
documentHandler := handlers.NewDocumentHandler(queries)
|
||||
// Only pageHandler is needed
|
||||
pageHandler := handlers.NewPageHandler(queries, tmpl)
|
||||
addressHandler := handlers.NewAddressHandler(queries)
|
||||
attachmentHandler := handlers.NewAttachmentHandler(queries)
|
||||
countryHandler := handlers.NewCountryHandler(queries)
|
||||
statusHandler := handlers.NewStatusHandler(queries)
|
||||
lineItemHandler := handlers.NewLineItemHandler(queries)
|
||||
|
||||
// Setup routes
|
||||
r := mux.NewRouter()
|
||||
goRouter := r.PathPrefix("/go").Subrouter()
|
||||
|
||||
// Static files
|
||||
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||
goRouter.PathPrefix("/static/").Handler(http.StripPrefix("/go/static/", http.FileServer(http.Dir("static"))))
|
||||
|
||||
// PDF files (matching CakePHP structure)
|
||||
r.PathPrefix("/pdf/").Handler(http.StripPrefix("/pdf/", http.FileServer(http.Dir("webroot/pdf"))))
|
||||
// PDF files
|
||||
goRouter.PathPrefix("/pdf/").Handler(http.StripPrefix("/go/pdf/", http.FileServer(http.Dir("webroot/pdf"))))
|
||||
|
||||
goRouter.HandleFunc("/quotes", pageHandler.QuotesView).Methods("GET")
|
||||
|
||||
// The following routes are currently disabled:
|
||||
/*
|
||||
// API routes
|
||||
api := r.PathPrefix("/api/v1").Subrouter()
|
||||
|
||||
// Customer routes
|
||||
api.HandleFunc("/customers", customerHandler.List).Methods("GET")
|
||||
api.HandleFunc("/customers", customerHandler.Create).Methods("POST")
|
||||
api.HandleFunc("/customers/{id}", customerHandler.Get).Methods("GET")
|
||||
api.HandleFunc("/customers/{id}", customerHandler.Update).Methods("PUT")
|
||||
api.HandleFunc("/customers/{id}", customerHandler.Delete).Methods("DELETE")
|
||||
api.HandleFunc("/customers/search", customerHandler.Search).Methods("GET")
|
||||
|
||||
// Product routes
|
||||
api.HandleFunc("/products", productHandler.List).Methods("GET")
|
||||
api.HandleFunc("/products", productHandler.Create).Methods("POST")
|
||||
|
|
@ -92,7 +84,6 @@ func main() {
|
|||
api.HandleFunc("/products/{id}", productHandler.Update).Methods("PUT")
|
||||
api.HandleFunc("/products/{id}", productHandler.Delete).Methods("DELETE")
|
||||
api.HandleFunc("/products/search", productHandler.Search).Methods("GET")
|
||||
|
||||
// Purchase Order routes
|
||||
api.HandleFunc("/purchase-orders", purchaseOrderHandler.List).Methods("GET")
|
||||
api.HandleFunc("/purchase-orders", purchaseOrderHandler.Create).Methods("POST")
|
||||
|
|
@ -100,7 +91,6 @@ func main() {
|
|||
api.HandleFunc("/purchase-orders/{id}", purchaseOrderHandler.Update).Methods("PUT")
|
||||
api.HandleFunc("/purchase-orders/{id}", purchaseOrderHandler.Delete).Methods("DELETE")
|
||||
api.HandleFunc("/purchase-orders/search", purchaseOrderHandler.Search).Methods("GET")
|
||||
|
||||
// Enquiry routes
|
||||
api.HandleFunc("/enquiries", enquiryHandler.List).Methods("GET")
|
||||
api.HandleFunc("/enquiries", enquiryHandler.Create).Methods("POST")
|
||||
|
|
@ -111,7 +101,6 @@ func main() {
|
|||
api.HandleFunc("/enquiries/{id}/status", enquiryHandler.UpdateStatus).Methods("PUT")
|
||||
api.HandleFunc("/enquiries/{id}/mark-submitted", enquiryHandler.MarkSubmitted).Methods("PUT")
|
||||
api.HandleFunc("/enquiries/search", enquiryHandler.Search).Methods("GET")
|
||||
|
||||
// Document routes
|
||||
api.HandleFunc("/documents", documentHandler.List).Methods("GET")
|
||||
api.HandleFunc("/documents", documentHandler.Create).Methods("POST")
|
||||
|
|
@ -120,7 +109,6 @@ func main() {
|
|||
api.HandleFunc("/documents/{id}/archive", documentHandler.Archive).Methods("PUT")
|
||||
api.HandleFunc("/documents/{id}/unarchive", documentHandler.Unarchive).Methods("PUT")
|
||||
api.HandleFunc("/documents/search", documentHandler.Search).Methods("GET")
|
||||
|
||||
// Address routes
|
||||
api.HandleFunc("/addresses", addressHandler.List).Methods("GET")
|
||||
api.HandleFunc("/addresses", addressHandler.Create).Methods("POST")
|
||||
|
|
@ -128,7 +116,6 @@ func main() {
|
|||
api.HandleFunc("/addresses/{id}", addressHandler.Update).Methods("PUT")
|
||||
api.HandleFunc("/addresses/{id}", addressHandler.Delete).Methods("DELETE")
|
||||
api.HandleFunc("/addresses/customer/{customerID}", addressHandler.CustomerAddresses).Methods("GET")
|
||||
|
||||
// Attachment routes
|
||||
api.HandleFunc("/attachments", attachmentHandler.List).Methods("GET")
|
||||
api.HandleFunc("/attachments/archived", attachmentHandler.Archived).Methods("GET")
|
||||
|
|
@ -136,7 +123,6 @@ func main() {
|
|||
api.HandleFunc("/attachments/{id}", attachmentHandler.Get).Methods("GET")
|
||||
api.HandleFunc("/attachments/{id}", attachmentHandler.Update).Methods("PUT")
|
||||
api.HandleFunc("/attachments/{id}", attachmentHandler.Delete).Methods("DELETE")
|
||||
|
||||
// Country routes
|
||||
api.HandleFunc("/countries", countryHandler.List).Methods("GET")
|
||||
api.HandleFunc("/countries", countryHandler.Create).Methods("POST")
|
||||
|
|
@ -144,7 +130,6 @@ func main() {
|
|||
api.HandleFunc("/countries/{id}", countryHandler.Update).Methods("PUT")
|
||||
api.HandleFunc("/countries/{id}", countryHandler.Delete).Methods("DELETE")
|
||||
api.HandleFunc("/countries/complete", countryHandler.CompleteCountry).Methods("GET")
|
||||
|
||||
// Status routes
|
||||
api.HandleFunc("/statuses", statusHandler.List).Methods("GET")
|
||||
api.HandleFunc("/statuses", statusHandler.Create).Methods("POST")
|
||||
|
|
@ -152,7 +137,6 @@ func main() {
|
|||
api.HandleFunc("/statuses/{id}", statusHandler.Update).Methods("PUT")
|
||||
api.HandleFunc("/statuses/{id}", statusHandler.Delete).Methods("DELETE")
|
||||
api.HandleFunc("/statuses/json/{selectedId}", statusHandler.JsonList).Methods("GET")
|
||||
|
||||
// Line Item routes
|
||||
api.HandleFunc("/line-items", lineItemHandler.List).Methods("GET")
|
||||
api.HandleFunc("/line-items", lineItemHandler.Create).Methods("POST")
|
||||
|
|
@ -160,54 +144,45 @@ func main() {
|
|||
api.HandleFunc("/line-items/{id}", lineItemHandler.Update).Methods("PUT")
|
||||
api.HandleFunc("/line-items/{id}", lineItemHandler.Delete).Methods("DELETE")
|
||||
api.HandleFunc("/line-items/document/{documentID}/table", lineItemHandler.GetTable).Methods("GET")
|
||||
|
||||
// Health check
|
||||
api.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"status":"ok"}`))
|
||||
}).Methods("GET")
|
||||
|
||||
// Recent activity endpoint
|
||||
r.HandleFunc("/api/recent-activity", documentHandler.GetRecentActivity).Methods("GET")
|
||||
|
||||
// Page routes
|
||||
r.HandleFunc("/", pageHandler.Home).Methods("GET")
|
||||
|
||||
// Customer pages
|
||||
r.HandleFunc("/customers", pageHandler.CustomersIndex).Methods("GET")
|
||||
r.HandleFunc("/customers/new", pageHandler.CustomersNew).Methods("GET")
|
||||
r.HandleFunc("/customers/search", pageHandler.CustomersSearch).Methods("GET")
|
||||
r.HandleFunc("/customers/{id}", pageHandler.CustomersShow).Methods("GET")
|
||||
r.HandleFunc("/customers/{id}/edit", pageHandler.CustomersEdit).Methods("GET")
|
||||
|
||||
// Product pages
|
||||
r.HandleFunc("/products", pageHandler.ProductsIndex).Methods("GET")
|
||||
r.HandleFunc("/products/new", pageHandler.ProductsNew).Methods("GET")
|
||||
r.HandleFunc("/products/search", pageHandler.ProductsSearch).Methods("GET")
|
||||
r.HandleFunc("/products/{id}", pageHandler.ProductsShow).Methods("GET")
|
||||
r.HandleFunc("/products/{id}/edit", pageHandler.ProductsEdit).Methods("GET")
|
||||
|
||||
// Purchase Order pages
|
||||
r.HandleFunc("/purchase-orders", pageHandler.PurchaseOrdersIndex).Methods("GET")
|
||||
r.HandleFunc("/purchase-orders/new", pageHandler.PurchaseOrdersNew).Methods("GET")
|
||||
r.HandleFunc("/purchase-orders/search", pageHandler.PurchaseOrdersSearch).Methods("GET")
|
||||
r.HandleFunc("/purchase-orders/{id}", pageHandler.PurchaseOrdersShow).Methods("GET")
|
||||
r.HandleFunc("/purchase-orders/{id}/edit", pageHandler.PurchaseOrdersEdit).Methods("GET")
|
||||
|
||||
// Enquiry pages
|
||||
r.HandleFunc("/enquiries", pageHandler.EnquiriesIndex).Methods("GET")
|
||||
r.HandleFunc("/enquiries/new", pageHandler.EnquiriesNew).Methods("GET")
|
||||
r.HandleFunc("/enquiries/search", pageHandler.EnquiriesSearch).Methods("GET")
|
||||
r.HandleFunc("/enquiries/{id}", pageHandler.EnquiriesShow).Methods("GET")
|
||||
r.HandleFunc("/enquiries/{id}/edit", pageHandler.EnquiriesEdit).Methods("GET")
|
||||
|
||||
// Document pages
|
||||
r.HandleFunc("/documents", pageHandler.DocumentsIndex).Methods("GET")
|
||||
r.HandleFunc("/documents/search", pageHandler.DocumentsSearch).Methods("GET")
|
||||
r.HandleFunc("/documents/view/{id}", pageHandler.DocumentsView).Methods("GET")
|
||||
r.HandleFunc("/documents/{id}", pageHandler.DocumentsShow).Methods("GET")
|
||||
r.HandleFunc("/documents/pdf/{id}", documentHandler.GeneratePDF).Methods("GET")
|
||||
|
||||
// Address routes (matching CakePHP)
|
||||
r.HandleFunc("/addresses", addressHandler.List).Methods("GET")
|
||||
r.HandleFunc("/addresses/view/{id}", addressHandler.Get).Methods("GET")
|
||||
|
|
@ -216,7 +191,6 @@ func main() {
|
|||
r.HandleFunc("/addresses/remove_another/{increment}", addressHandler.RemoveAnother).Methods("GET")
|
||||
r.HandleFunc("/addresses/edit/{id}", addressHandler.Update).Methods("GET", "POST")
|
||||
r.HandleFunc("/addresses/customer_addresses/{customerID}", addressHandler.CustomerAddresses).Methods("GET")
|
||||
|
||||
// Attachment routes (matching CakePHP)
|
||||
r.HandleFunc("/attachments", attachmentHandler.List).Methods("GET")
|
||||
r.HandleFunc("/attachments/view/{id}", attachmentHandler.Get).Methods("GET")
|
||||
|
|
@ -224,7 +198,6 @@ func main() {
|
|||
r.HandleFunc("/attachments/add", attachmentHandler.Create).Methods("GET", "POST")
|
||||
r.HandleFunc("/attachments/edit/{id}", attachmentHandler.Update).Methods("GET", "POST")
|
||||
r.HandleFunc("/attachments/delete/{id}", attachmentHandler.Delete).Methods("POST")
|
||||
|
||||
// Country routes (matching CakePHP)
|
||||
r.HandleFunc("/countries", countryHandler.List).Methods("GET")
|
||||
r.HandleFunc("/countries/view/{id}", countryHandler.Get).Methods("GET")
|
||||
|
|
@ -232,7 +205,6 @@ func main() {
|
|||
r.HandleFunc("/countries/edit/{id}", countryHandler.Update).Methods("GET", "POST")
|
||||
r.HandleFunc("/countries/delete/{id}", countryHandler.Delete).Methods("POST")
|
||||
r.HandleFunc("/countries/complete_country", countryHandler.CompleteCountry).Methods("GET")
|
||||
|
||||
// Status routes (matching CakePHP)
|
||||
r.HandleFunc("/statuses", statusHandler.List).Methods("GET")
|
||||
r.HandleFunc("/statuses/view/{id}", statusHandler.Get).Methods("GET")
|
||||
|
|
@ -240,7 +212,6 @@ func main() {
|
|||
r.HandleFunc("/statuses/edit/{id}", statusHandler.Update).Methods("GET", "POST")
|
||||
r.HandleFunc("/statuses/delete/{id}", statusHandler.Delete).Methods("POST")
|
||||
r.HandleFunc("/statuses/json_list/{selectedId}", statusHandler.JsonList).Methods("GET")
|
||||
|
||||
// Line Item routes (matching CakePHP)
|
||||
r.HandleFunc("/line_items/ajax_add", lineItemHandler.AjaxAdd).Methods("POST")
|
||||
r.HandleFunc("/line_items/ajax_edit", lineItemHandler.AjaxEdit).Methods("POST")
|
||||
|
|
@ -248,31 +219,33 @@ func main() {
|
|||
r.HandleFunc("/line_items/get_table/{documentID}", lineItemHandler.GetTable).Methods("GET")
|
||||
r.HandleFunc("/line_items/edit/{id}", lineItemHandler.Update).Methods("GET", "POST")
|
||||
r.HandleFunc("/line_items/add/{documentID}", lineItemHandler.Create).Methods("GET", "POST")
|
||||
|
||||
// HTMX endpoints
|
||||
r.HandleFunc("/customers", customerHandler.Create).Methods("POST")
|
||||
r.HandleFunc("/customers/{id}", customerHandler.Update).Methods("PUT")
|
||||
r.HandleFunc("/customers/{id}", customerHandler.Delete).Methods("DELETE")
|
||||
|
||||
r.HandleFunc("/products", productHandler.Create).Methods("POST")
|
||||
r.HandleFunc("/products/{id}", productHandler.Update).Methods("PUT")
|
||||
r.HandleFunc("/products/{id}", productHandler.Delete).Methods("DELETE")
|
||||
|
||||
r.HandleFunc("/purchase-orders", purchaseOrderHandler.Create).Methods("POST")
|
||||
r.HandleFunc("/purchase-orders/{id}", purchaseOrderHandler.Update).Methods("PUT")
|
||||
r.HandleFunc("/purchase-orders/{id}", purchaseOrderHandler.Delete).Methods("DELETE")
|
||||
|
||||
r.HandleFunc("/enquiries", enquiryHandler.Create).Methods("POST")
|
||||
r.HandleFunc("/enquiries/{id}", enquiryHandler.Update).Methods("PUT")
|
||||
r.HandleFunc("/enquiries/{id}", enquiryHandler.Delete).Methods("DELETE")
|
||||
r.HandleFunc("/enquiries/{id}/undelete", enquiryHandler.Undelete).Methods("PUT")
|
||||
r.HandleFunc("/enquiries/{id}/status", enquiryHandler.UpdateStatus).Methods("PUT")
|
||||
r.HandleFunc("/enquiries/{id}/mark-submitted", enquiryHandler.MarkSubmitted).Methods("PUT")
|
||||
|
||||
r.HandleFunc("/documents", documentHandler.Create).Methods("POST")
|
||||
r.HandleFunc("/documents/{id}", documentHandler.Update).Methods("PUT")
|
||||
r.HandleFunc("/documents/{id}/archive", documentHandler.Archive).Methods("PUT")
|
||||
r.HandleFunc("/documents/{id}/unarchive", documentHandler.Unarchive).Methods("PUT")
|
||||
*/
|
||||
|
||||
// Catch-all for everything else
|
||||
r.PathPrefix("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte("404 page not found"))
|
||||
})
|
||||
|
||||
// Start server
|
||||
port := getEnv("PORT", "8080")
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
module code.springupsoftware.com/cmc/cmc-sales
|
||||
|
||||
go 1.23
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.3
|
||||
|
||||
require (
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/jung-kurt/gofpdf v1.16.2
|
||||
golang.org/x/text v0.27.0
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,3 +16,5 @@ github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfF
|
|||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import (
|
|||
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/db"
|
||||
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/templates"
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type PageHandler struct {
|
||||
|
|
@ -22,10 +24,21 @@ func NewPageHandler(queries *db.Queries, tmpl *templates.TemplateManager) *PageH
|
|||
}
|
||||
}
|
||||
|
||||
// Helper function to get the username from the request
|
||||
func getUsername(r *http.Request) string {
|
||||
username, _, ok := r.BasicAuth()
|
||||
if ok && username != "" {
|
||||
caser := cases.Title(language.English)
|
||||
return caser.String(username) // Capitalise the username for display
|
||||
}
|
||||
return "Guest"
|
||||
}
|
||||
|
||||
// Home page
|
||||
func (h *PageHandler) Home(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{
|
||||
"Title": "Dashboard",
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
if err := h.tmpl.Render(w, "index.html", data); err != nil {
|
||||
|
|
@ -65,6 +78,7 @@ func (h *PageHandler) CustomersIndex(w http.ResponseWriter, r *http.Request) {
|
|||
"PrevPage": page - 1,
|
||||
"NextPage": page + 1,
|
||||
"HasMore": hasMore,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
// Check if this is an HTMX request
|
||||
|
|
@ -83,6 +97,7 @@ func (h *PageHandler) CustomersIndex(w http.ResponseWriter, r *http.Request) {
|
|||
func (h *PageHandler) CustomersNew(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{
|
||||
"Customer": db.Customer{},
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
if err := h.tmpl.Render(w, "customers/form.html", data); err != nil {
|
||||
|
|
@ -106,6 +121,7 @@ func (h *PageHandler) CustomersEdit(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
data := map[string]interface{}{
|
||||
"Customer": customer,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
if err := h.tmpl.Render(w, "customers/form.html", data); err != nil {
|
||||
|
|
@ -129,6 +145,7 @@ func (h *PageHandler) CustomersShow(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
data := map[string]interface{}{
|
||||
"Customer": customer,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
if err := h.tmpl.Render(w, "customers/show.html", data); err != nil {
|
||||
|
|
@ -181,6 +198,7 @@ func (h *PageHandler) CustomersSearch(w http.ResponseWriter, r *http.Request) {
|
|||
"PrevPage": page - 1,
|
||||
"NextPage": page + 1,
|
||||
"HasMore": hasMore,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
|
|
@ -194,6 +212,7 @@ func (h *PageHandler) ProductsIndex(w http.ResponseWriter, r *http.Request) {
|
|||
// Similar implementation to CustomersIndex but for products
|
||||
data := map[string]interface{}{
|
||||
"Products": []db.Product{}, // Placeholder
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
if err := h.tmpl.Render(w, "products/index.html", data); err != nil {
|
||||
|
|
@ -204,6 +223,7 @@ func (h *PageHandler) ProductsIndex(w http.ResponseWriter, r *http.Request) {
|
|||
func (h *PageHandler) ProductsNew(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{
|
||||
"Product": db.Product{},
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
if err := h.tmpl.Render(w, "products/form.html", data); err != nil {
|
||||
|
|
@ -227,6 +247,7 @@ func (h *PageHandler) ProductsShow(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
data := map[string]interface{}{
|
||||
"Product": product,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
if err := h.tmpl.Render(w, "products/show.html", data); err != nil {
|
||||
|
|
@ -250,6 +271,7 @@ func (h *PageHandler) ProductsEdit(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
data := map[string]interface{}{
|
||||
"Product": product,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
if err := h.tmpl.Render(w, "products/form.html", data); err != nil {
|
||||
|
|
@ -261,6 +283,7 @@ func (h *PageHandler) ProductsSearch(w http.ResponseWriter, r *http.Request) {
|
|||
// Similar to CustomersSearch but for products
|
||||
data := map[string]interface{}{
|
||||
"Products": []db.Product{},
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
|
|
@ -273,6 +296,7 @@ func (h *PageHandler) ProductsSearch(w http.ResponseWriter, r *http.Request) {
|
|||
func (h *PageHandler) PurchaseOrdersIndex(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{
|
||||
"PurchaseOrders": []db.PurchaseOrder{},
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
if err := h.tmpl.Render(w, "purchase-orders/index.html", data); err != nil {
|
||||
|
|
@ -283,6 +307,7 @@ func (h *PageHandler) PurchaseOrdersIndex(w http.ResponseWriter, r *http.Request
|
|||
func (h *PageHandler) PurchaseOrdersNew(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{
|
||||
"PurchaseOrder": db.PurchaseOrder{},
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
if err := h.tmpl.Render(w, "purchase-orders/form.html", data); err != nil {
|
||||
|
|
@ -306,6 +331,7 @@ func (h *PageHandler) PurchaseOrdersShow(w http.ResponseWriter, r *http.Request)
|
|||
|
||||
data := map[string]interface{}{
|
||||
"PurchaseOrder": purchaseOrder,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
if err := h.tmpl.Render(w, "purchase-orders/show.html", data); err != nil {
|
||||
|
|
@ -329,6 +355,7 @@ func (h *PageHandler) PurchaseOrdersEdit(w http.ResponseWriter, r *http.Request)
|
|||
|
||||
data := map[string]interface{}{
|
||||
"PurchaseOrder": purchaseOrder,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
if err := h.tmpl.Render(w, "purchase-orders/form.html", data); err != nil {
|
||||
|
|
@ -339,6 +366,7 @@ func (h *PageHandler) PurchaseOrdersEdit(w http.ResponseWriter, r *http.Request)
|
|||
func (h *PageHandler) PurchaseOrdersSearch(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{
|
||||
"PurchaseOrders": []db.PurchaseOrder{},
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
|
|
@ -409,6 +437,7 @@ func (h *PageHandler) EnquiriesIndex(w http.ResponseWriter, r *http.Request) {
|
|||
"PrevPage": page - 1,
|
||||
"NextPage": page + 1,
|
||||
"HasMore": hasMore,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
// Check if this is an HTMX request
|
||||
|
|
@ -456,6 +485,7 @@ func (h *PageHandler) EnquiriesNew(w http.ResponseWriter, r *http.Request) {
|
|||
"Principles": principles,
|
||||
"States": states,
|
||||
"Countries": countries,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
if err := h.tmpl.Render(w, "enquiries/form.html", data); err != nil {
|
||||
|
|
@ -479,6 +509,7 @@ func (h *PageHandler) EnquiriesShow(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
data := map[string]interface{}{
|
||||
"Enquiry": enquiry,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
if err := h.tmpl.Render(w, "enquiries/show.html", data); err != nil {
|
||||
|
|
@ -531,6 +562,7 @@ func (h *PageHandler) EnquiriesEdit(w http.ResponseWriter, r *http.Request) {
|
|||
"Principles": principles,
|
||||
"States": states,
|
||||
"Countries": countries,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
if err := h.tmpl.Render(w, "enquiries/form.html", data); err != nil {
|
||||
|
|
@ -595,6 +627,7 @@ func (h *PageHandler) EnquiriesSearch(w http.ResponseWriter, r *http.Request) {
|
|||
"PrevPage": page - 1,
|
||||
"NextPage": page + 1,
|
||||
"HasMore": hasMore,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
|
|
@ -651,6 +684,7 @@ func (h *PageHandler) DocumentsIndex(w http.ResponseWriter, r *http.Request) {
|
|||
"Users": users,
|
||||
"Page": page,
|
||||
"DocType": docType,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
// Check if this is an HTMX request
|
||||
|
|
@ -683,6 +717,7 @@ func (h *PageHandler) DocumentsShow(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
data := map[string]interface{}{
|
||||
"Document": document,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
if err := h.tmpl.Render(w, "documents/show.html", data); err != nil {
|
||||
|
|
@ -711,6 +746,7 @@ func (h *PageHandler) DocumentsSearch(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
data := map[string]interface{}{
|
||||
"Documents": documents,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
|
|
@ -746,6 +782,7 @@ func (h *PageHandler) DocumentsView(w http.ResponseWriter, r *http.Request) {
|
|||
"Document": document,
|
||||
"DocType": string(document.Type),
|
||||
"LineItems": lineItems,
|
||||
"User": getUsername(r),
|
||||
}
|
||||
|
||||
// Add document type specific data
|
||||
|
|
@ -776,3 +813,10 @@ func (h *PageHandler) DocumentsView(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// Quotes view page
|
||||
func (h *PageHandler) QuotesView(w http.ResponseWriter, r *http.Request) {
|
||||
if err := h.tmpl.Render(w, "quotes/index.html", nil); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ func NewTemplateManager(templatesDir string) (*TemplateManager, error) {
|
|||
"formatDate": formatDate,
|
||||
"truncate": truncate,
|
||||
"currency": formatCurrency,
|
||||
"currentYear": func() int { return time.Now().Year() },
|
||||
}
|
||||
|
||||
// Load all templates
|
||||
|
|
@ -38,6 +39,7 @@ func NewTemplateManager(templatesDir string) (*TemplateManager, error) {
|
|||
|
||||
// Load page templates
|
||||
pages := []string{
|
||||
"index.html",
|
||||
"customers/index.html",
|
||||
"customers/show.html",
|
||||
"customers/form.html",
|
||||
|
|
@ -63,7 +65,7 @@ func NewTemplateManager(templatesDir string) (*TemplateManager, error) {
|
|||
"documents/purchase-order-view.html",
|
||||
"documents/orderack-view.html",
|
||||
"documents/packinglist-view.html",
|
||||
"index.html",
|
||||
"quotes/index.html",
|
||||
}
|
||||
|
||||
for _, page := range pages {
|
||||
|
|
|
|||
BIN
go-app/server
BIN
go-app/server
Binary file not shown.
|
|
@ -6,8 +6,32 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{block "title" .}}CMC Sales{{end}}</title>
|
||||
|
||||
<!-- Bulma CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
|
||||
<!-- Tailwind CSS CDN -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
cmcblue: '#4686c3',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
/* Additional CSS to assist with more complicated transitions for the nav bar items */
|
||||
.navbar-dropdown {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 80ms ease, visibility 80ms ease;
|
||||
}
|
||||
.group:hover .navbar-dropdown,
|
||||
.group:focus-within .navbar-dropdown {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
|
@ -20,65 +44,153 @@
|
|||
|
||||
{{block "head" .}}{{end}}
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar is-primary" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
<strong>CMC Sales</strong>
|
||||
</a>
|
||||
|
||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarMain">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<body class="min-h-screen flex flex-col">
|
||||
<!-- Cute Light Blue Navigation -->
|
||||
<nav class="bg-cmcblue shadow flex items-center justify-between px-4 py-2" role="navigation" aria-label="main navigation">
|
||||
<div class="flex items-center space-x-2">
|
||||
<a class="flex items-center space-x-2 text-white font-bold text-lg" href="/">
|
||||
<span><i class="fas fa-flask text-cmcblue bg-white rounded p-1"></i></span>
|
||||
<span>CMC Sales</span>
|
||||
</a>
|
||||
<span class="mx-4 h-8 w-px bg-white opacity-40"></span>
|
||||
</div>
|
||||
|
||||
<div id="navbarMain" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<a class="navbar-item" href="/">
|
||||
<span class="icon"><i class="fas fa-home"></i></span>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
|
||||
<a class="navbar-item" href="/customers">
|
||||
<span class="icon"><i class="fas fa-users"></i></span>
|
||||
<span>Customers</span>
|
||||
</a>
|
||||
|
||||
<a class="navbar-item" href="/products">
|
||||
<span class="icon"><i class="fas fa-box"></i></span>
|
||||
<span>Products</span>
|
||||
</a>
|
||||
|
||||
<a class="navbar-item" href="/purchase-orders">
|
||||
<span class="icon"><i class="fas fa-file-invoice"></i></span>
|
||||
<span>Purchase Orders</span>
|
||||
</a>
|
||||
|
||||
<a class="navbar-item" href="/enquiries">
|
||||
<span class="icon"><i class="fas fa-envelope"></i></span>
|
||||
<div class="flex-1 flex items-center">
|
||||
<ul class="flex space-x-2">
|
||||
<li class="relative group">
|
||||
<a class="flex items-center space-x-1 text-white px-2 py-1 bg-cmcblue cursor-pointer rounded transition duration-200 ease-in-out" href="/enquiries/index">
|
||||
<span>Enquiries</span>
|
||||
<span><i class="ml-1 fas fa-envelope text-white"></i></span>
|
||||
</a>
|
||||
<ul class="navbar-dropdown absolute left-0 top-[calc(100%_-_2px)] w-48 bg-white shadow-lg rounded z-10">
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/enquiries/index">Enquiry Register</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="relative group">
|
||||
<a class="flex items-center space-x-1 text-white px-2 py-1 bg-cmcblue cursor-pointer rounded transition duration-200 ease-in-out" href="/documents/index">
|
||||
<span>Documents</span>
|
||||
<span><i class="ml-1 fas fa-file-alt text-white"></i></span>
|
||||
</a>
|
||||
<ul class="navbar-dropdown absolute left-0 top-[calc(100%_-_2px)] w-48 bg-white shadow-lg rounded z-10">
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/documents/index">Documents Index</a></li>
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/go/quotes">Quotes</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="relative group">
|
||||
<a class="flex items-center space-x-1 text-white px-2 py-1 bg-cmcblue cursor-pointer rounded transition duration-200 ease-in-out" href="/jobs/index">
|
||||
<span>Jobs</span>
|
||||
<span><i class="ml-1 fas fa-briefcase text-white"></i></span>
|
||||
</a>
|
||||
<ul class="navbar-dropdown absolute left-0 top-[calc(100%_-_2px)] w-48 bg-white shadow-lg rounded z-10">
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/jobs/reports">Reports</a></li>
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/jobs/index">Job List</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="relative group">
|
||||
<a class="flex items-center space-x-1 text-white px-2 py-1 bg-cmcblue cursor-pointer rounded transition duration-200 ease-in-out" href="/shipments/index">
|
||||
<span>Shipments</span>
|
||||
<span><i class="ml-1 fas fa-truck text-white"></i></span>
|
||||
</a>
|
||||
<ul class="navbar-dropdown absolute left-0 top-[calc(100%_-_2px)] w-64 bg-white shadow-lg rounded z-10">
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/shipments/index">All Shipments</a></li>
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/shipments/index/import">Import Shipments</a></li>
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/shipments/index/direct">Direct Shipments</a></li>
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/shipments/index/export">Export Shipments</a></li>
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/shipments/index/local">Local Shipments</a></li>
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/shipments/reports">Monthly Deferred GST</a></li>
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/freight_forwarders">Freight Forwarders</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="relative group">
|
||||
<a class="flex items-center space-x-1 text-white px-2 py-1 bg-cmcblue cursor-pointer rounded transition duration-200 ease-in-out" href="/customers/index">
|
||||
<span>Customers</span>
|
||||
<span><i class="ml-1 fas fa-users text-white"></i></span>
|
||||
</a>
|
||||
<ul class="navbar-dropdown absolute left-0 top-[calc(100%_-_2px)] w-56 bg-white shadow-lg rounded z-10">
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/customers/index">Customer Index</a></li>
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/customers/add">Add Customer</a></li>
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/industries/index">Industries</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="relative group">
|
||||
<a class="flex items-center space-x-1 text-white px-2 py-1 bg-cmcblue cursor-pointer rounded transition duration-200 ease-in-out" href="/purchase_orders/index">
|
||||
<span>POs</span>
|
||||
<span><i class="ml-1 fas fa-file-invoice text-white"></i></span>
|
||||
</a>
|
||||
<ul class="navbar-dropdown absolute left-0 top-[calc(100%_-_2px)] w-48 bg-white shadow-lg rounded z-10">
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/purchase_orders/index">PO Index</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="relative group">
|
||||
<a class="flex items-center space-x-1 text-white px-2 py-1 bg-cmcblue cursor-pointer rounded transition duration-200 ease-in-out" href="/invoices/index">
|
||||
<span>Invoices</span>
|
||||
<span><i class="ml-1 fas fa-receipt text-white"></i></span>
|
||||
</a>
|
||||
<ul class="navbar-dropdown absolute left-0 top-[calc(100%_-_2px)] w-56 bg-white shadow-lg rounded z-10">
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/invoices/index">Invoices Index</a></li>
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/invoices/printView">Print View</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="relative group">
|
||||
<a class="flex items-center space-x-1 text-white px-2 py-1 bg-cmcblue cursor-pointer rounded transition duration-200 ease-in-out" href="/products/index">
|
||||
<span>Products</span>
|
||||
<span><i class="ml-1 fas fa-box text-white"></i></span>
|
||||
</a>
|
||||
<ul class="navbar-dropdown absolute left-0 top-[calc(100%_-_2px)] w-48 bg-white shadow-lg rounded z-10">
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/products/index">Product Index</a></li>
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/products/add">Add Product</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="relative group">
|
||||
<a class="flex items-center space-x-1 text-white px-2 py-1 bg-cmcblue cursor-pointer rounded transition duration-200 ease-in-out" href="/principles/index">
|
||||
<span>Principles</span>
|
||||
<span><i class="ml-1 fas fa-user-tie text-white"></i></span>
|
||||
</a>
|
||||
<ul class="navbar-dropdown absolute left-0 top-[calc(100%_-_2px)] w-56 bg-white shadow-lg rounded z-10">
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/principles/index">Principle Index</a></li>
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/attachments/index">Attachments</a></li>
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/principles/add">Add Principle</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a class="flex items-center space-x-1 text-white px-2 py-1 bg-cmcblue cursor-pointer rounded transition duration-200 ease-in-out" href="/enquiries/search" id="searchLink">
|
||||
<span>Search</span>
|
||||
<span><i class="ml-1 fas fa-search text-white"></i></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="relative group">
|
||||
<a class="flex items-center space-x-1 text-white px-2 py-1 bg-cmcblue cursor-pointer rounded transition duration-200 ease-in-out" href="/pages/about">
|
||||
<span>Help</span>
|
||||
<span><i class="ml-1 fas fa-question-circle text-white"></i></span>
|
||||
</a>
|
||||
<ul class="navbar-dropdown absolute left-0 top-[calc(100%_-_2px)] w-48 bg-white shadow-lg rounded z-10">
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/pages/bug">Raise a bug</a></li>
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="https://gitlab.com/minimalist.software/cmc-sales/issues">Issue tracker</a></li>
|
||||
<li><a class="block px-4 py-2 text-cmcblue hover:bg-cmcblue/10" href="/pages/about">About</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a class="flex items-center space-x-1 text-white px-2 py-1 bg-cmcblue cursor-pointer rounded transition duration-200 ease-in-out" href="/admin">
|
||||
<span>Admin</span>
|
||||
<span><i class="ml-1 fas fa-cog text-white"></i></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<span><i class="fas fa-user text-cmcblue bg-white rounded p-1"></i></span>
|
||||
<span class="text-white">{{if .User}}{{.User}}{{else}}Guest{{end}}</span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<section class="section flex-1">
|
||||
<div class="container p-4">
|
||||
{{block "content" .}}{{end}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="content has-text-centered">
|
||||
<p>
|
||||
<strong>CMC Sales</strong> © 2024 CMC Technologies
|
||||
</p>
|
||||
</div>
|
||||
<footer class="bg-slate-500 text-white text-center py-2 text-sm mt-8 w-full">
|
||||
<strong>CMC Sales</strong> © {{ currentYear }}
|
||||
</footer>
|
||||
|
||||
<!-- Custom JS -->
|
||||
|
|
|
|||
4
go-app/templates/quotes/index.html
Normal file
4
go-app/templates/quotes/index.html
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{{define "content"}}
|
||||
<h1 class="text-2xl font-bold mb-4">Quotes</h1>
|
||||
<p>Welcome to the Quotes page. Here you can view and manage your quotes.</p>
|
||||
{{end}}
|
||||
1
go-app/tmp/stdout
Normal file
1
go-app/tmp/stdout
Normal file
|
|
@ -0,0 +1 @@
|
|||
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
|
||||
Loading…
Reference in a new issue