2025-06-24 03:32:28 -07:00
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
import (
|
2025-07-02 05:04:36 -07:00
|
|
|
"log"
|
2025-06-24 03:32:28 -07:00
|
|
|
"net/http"
|
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
|
|
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/db"
|
|
|
|
|
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/templates"
|
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type PageHandler struct {
|
2025-07-02 05:04:36 -07:00
|
|
|
queries *db.Queries
|
|
|
|
|
tmpl *templates.TemplateManager
|
2025-06-24 03:32:28 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewPageHandler(queries *db.Queries, tmpl *templates.TemplateManager) *PageHandler {
|
|
|
|
|
return &PageHandler{
|
|
|
|
|
queries: queries,
|
|
|
|
|
tmpl: tmpl,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Home page
|
|
|
|
|
func (h *PageHandler) Home(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Title": "Dashboard",
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "index.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Customer pages
|
|
|
|
|
func (h *PageHandler) CustomersIndex(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
page := 1
|
|
|
|
|
if p := r.URL.Query().Get("page"); p != "" {
|
|
|
|
|
if val, err := strconv.Atoi(p); err == nil && val > 0 {
|
|
|
|
|
page = val
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
limit := 20
|
|
|
|
|
offset := (page - 1) * limit
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
customers, err := h.queries.ListCustomers(r.Context(), db.ListCustomersParams{
|
|
|
|
|
Limit: int32(limit + 1), // Get one extra to check if there are more
|
|
|
|
|
Offset: int32(offset),
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
hasMore := len(customers) > limit
|
|
|
|
|
if hasMore {
|
|
|
|
|
customers = customers[:limit]
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Customers": customers,
|
|
|
|
|
"Page": page,
|
|
|
|
|
"PrevPage": page - 1,
|
|
|
|
|
"NextPage": page + 1,
|
|
|
|
|
"HasMore": hasMore,
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
// Check if this is an HTMX request
|
|
|
|
|
if r.Header.Get("HX-Request") == "true" {
|
|
|
|
|
if err := h.tmpl.Render(w, "customers/table.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "customers/index.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) CustomersNew(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Customer": db.Customer{},
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "customers/form.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) CustomersEdit(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
|
id, err := strconv.Atoi(vars["id"])
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Invalid customer ID", http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
customer, err := h.queries.GetCustomer(r.Context(), int32(id))
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Customer not found", http.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Customer": customer,
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "customers/form.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) CustomersShow(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
|
id, err := strconv.Atoi(vars["id"])
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Invalid customer ID", http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
customer, err := h.queries.GetCustomer(r.Context(), int32(id))
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Customer not found", http.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Customer": customer,
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "customers/show.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) CustomersSearch(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
query := r.URL.Query().Get("search")
|
|
|
|
|
page := 1
|
|
|
|
|
if p := r.URL.Query().Get("page"); p != "" {
|
|
|
|
|
if val, err := strconv.Atoi(p); err == nil && val > 0 {
|
|
|
|
|
page = val
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
limit := 20
|
|
|
|
|
offset := (page - 1) * limit
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
var customers []db.Customer
|
|
|
|
|
var err error
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if query == "" {
|
|
|
|
|
customers, err = h.queries.ListCustomers(r.Context(), db.ListCustomersParams{
|
|
|
|
|
Limit: int32(limit + 1),
|
|
|
|
|
Offset: int32(offset),
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
customers, err = h.queries.SearchCustomersByName(r.Context(), db.SearchCustomersByNameParams{
|
|
|
|
|
CONCAT: query,
|
|
|
|
|
CONCAT_2: query,
|
|
|
|
|
Limit: int32(limit + 1),
|
|
|
|
|
Offset: int32(offset),
|
|
|
|
|
})
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
hasMore := len(customers) > limit
|
|
|
|
|
if hasMore {
|
|
|
|
|
customers = customers[:limit]
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Customers": customers,
|
|
|
|
|
"Page": page,
|
|
|
|
|
"PrevPage": page - 1,
|
|
|
|
|
"NextPage": page + 1,
|
|
|
|
|
"HasMore": hasMore,
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
|
|
|
if err := h.tmpl.Render(w, "customers/table.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Product page handlers
|
|
|
|
|
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
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "products/index.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) ProductsNew(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Product": db.Product{},
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "products/form.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) ProductsShow(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
|
id, err := strconv.Atoi(vars["id"])
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Invalid product ID", http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
product, err := h.queries.GetProduct(r.Context(), int32(id))
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Product not found", http.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Product": product,
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "products/show.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) ProductsEdit(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
|
id, err := strconv.Atoi(vars["id"])
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Invalid product ID", http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
product, err := h.queries.GetProduct(r.Context(), int32(id))
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Product not found", http.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Product": product,
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "products/form.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) ProductsSearch(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
// Similar to CustomersSearch but for products
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Products": []db.Product{},
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
|
|
|
if err := h.tmpl.Render(w, "products/table.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Purchase Order page handlers
|
|
|
|
|
func (h *PageHandler) PurchaseOrdersIndex(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"PurchaseOrders": []db.PurchaseOrder{},
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "purchase-orders/index.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) PurchaseOrdersNew(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"PurchaseOrder": db.PurchaseOrder{},
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "purchase-orders/form.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) PurchaseOrdersShow(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
|
id, err := strconv.Atoi(vars["id"])
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Invalid purchase order ID", http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
purchaseOrder, err := h.queries.GetPurchaseOrder(r.Context(), int32(id))
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Purchase order not found", http.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"PurchaseOrder": purchaseOrder,
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "purchase-orders/show.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) PurchaseOrdersEdit(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
|
id, err := strconv.Atoi(vars["id"])
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Invalid purchase order ID", http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
purchaseOrder, err := h.queries.GetPurchaseOrder(r.Context(), int32(id))
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Purchase order not found", http.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"PurchaseOrder": purchaseOrder,
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "purchase-orders/form.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) PurchaseOrdersSearch(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"PurchaseOrders": []db.PurchaseOrder{},
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
|
|
|
if err := h.tmpl.Render(w, "purchase-orders/table.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Enquiry page handlers
|
|
|
|
|
func (h *PageHandler) EnquiriesIndex(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
page := 1
|
|
|
|
|
if p := r.URL.Query().Get("page"); p != "" {
|
|
|
|
|
if val, err := strconv.Atoi(p); err == nil && val > 0 {
|
|
|
|
|
page = val
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
limit := 150
|
|
|
|
|
offset := (page - 1) * limit
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
var enquiries interface{}
|
|
|
|
|
var err error
|
|
|
|
|
var hasMore bool
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
// Check if we want archived enquiries
|
|
|
|
|
if r.URL.Query().Get("archived") == "true" {
|
|
|
|
|
archivedEnquiries, err := h.queries.ListArchivedEnquiries(r.Context(), db.ListArchivedEnquiriesParams{
|
|
|
|
|
Limit: int32(limit + 1),
|
|
|
|
|
Offset: int32(offset),
|
|
|
|
|
})
|
|
|
|
|
if err == nil {
|
|
|
|
|
hasMore = len(archivedEnquiries) > limit
|
|
|
|
|
if hasMore {
|
|
|
|
|
archivedEnquiries = archivedEnquiries[:limit]
|
|
|
|
|
}
|
|
|
|
|
enquiries = archivedEnquiries
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
activeEnquiries, err := h.queries.ListEnquiries(r.Context(), db.ListEnquiriesParams{
|
|
|
|
|
Limit: int32(limit + 1),
|
|
|
|
|
Offset: int32(offset),
|
|
|
|
|
})
|
|
|
|
|
if err == nil {
|
|
|
|
|
hasMore = len(activeEnquiries) > limit
|
|
|
|
|
if hasMore {
|
|
|
|
|
activeEnquiries = activeEnquiries[:limit]
|
|
|
|
|
}
|
|
|
|
|
enquiries = activeEnquiries
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
// Get status list for dropdown and CSS classes
|
|
|
|
|
statuses, err := h.queries.GetAllStatuses(r.Context())
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Enquiries": enquiries,
|
|
|
|
|
"Statuses": statuses,
|
|
|
|
|
"Page": page,
|
|
|
|
|
"PrevPage": page - 1,
|
|
|
|
|
"NextPage": page + 1,
|
|
|
|
|
"HasMore": hasMore,
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
// Check if this is an HTMX request
|
|
|
|
|
if r.Header.Get("HX-Request") == "true" {
|
|
|
|
|
if err := h.tmpl.RenderPartial(w, "enquiries/table.html", "enquiry-table", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "enquiries/index.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) EnquiriesNew(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
// Get required form data
|
|
|
|
|
statuses, err := h.queries.GetAllStatuses(r.Context())
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
principles, err := h.queries.GetAllPrinciples(r.Context())
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
states, err := h.queries.GetAllStates(r.Context())
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
countries, err := h.queries.GetAllCountries(r.Context())
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
data := map[string]interface{}{
|
2025-07-02 05:04:36 -07:00
|
|
|
"Enquiry": db.Enquiry{},
|
|
|
|
|
"Statuses": statuses,
|
2025-06-24 03:32:28 -07:00
|
|
|
"Principles": principles,
|
2025-07-02 05:04:36 -07:00
|
|
|
"States": states,
|
|
|
|
|
"Countries": countries,
|
2025-06-24 03:32:28 -07:00
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "enquiries/form.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) EnquiriesShow(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
|
id, err := strconv.Atoi(vars["id"])
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Invalid enquiry ID", http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
enquiry, err := h.queries.GetEnquiry(r.Context(), int32(id))
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Enquiry not found", http.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Enquiry": enquiry,
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "enquiries/show.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) EnquiriesEdit(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
|
id, err := strconv.Atoi(vars["id"])
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Invalid enquiry ID", http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
enquiry, err := h.queries.GetEnquiry(r.Context(), int32(id))
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Enquiry not found", http.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
// Get required form data
|
|
|
|
|
statuses, err := h.queries.GetAllStatuses(r.Context())
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
principles, err := h.queries.GetAllPrinciples(r.Context())
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
states, err := h.queries.GetAllStates(r.Context())
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
countries, err := h.queries.GetAllCountries(r.Context())
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
data := map[string]interface{}{
|
2025-07-02 05:04:36 -07:00
|
|
|
"Enquiry": enquiry,
|
|
|
|
|
"Statuses": statuses,
|
2025-06-24 03:32:28 -07:00
|
|
|
"Principles": principles,
|
2025-07-02 05:04:36 -07:00
|
|
|
"States": states,
|
|
|
|
|
"Countries": countries,
|
2025-06-24 03:32:28 -07:00
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if err := h.tmpl.Render(w, "enquiries/form.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) EnquiriesSearch(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
query := r.URL.Query().Get("search")
|
|
|
|
|
page := 1
|
|
|
|
|
if p := r.URL.Query().Get("page"); p != "" {
|
|
|
|
|
if val, err := strconv.Atoi(p); err == nil && val > 0 {
|
|
|
|
|
page = val
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
limit := 150
|
|
|
|
|
offset := (page - 1) * limit
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
var enquiries interface{}
|
|
|
|
|
var hasMore bool
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
if query == "" {
|
|
|
|
|
// If no search query, return regular list
|
|
|
|
|
regularEnquiries, err := h.queries.ListEnquiries(r.Context(), db.ListEnquiriesParams{
|
|
|
|
|
Limit: int32(limit + 1),
|
|
|
|
|
Offset: int32(offset),
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
hasMore = len(regularEnquiries) > limit
|
|
|
|
|
if hasMore {
|
|
|
|
|
regularEnquiries = regularEnquiries[:limit]
|
|
|
|
|
}
|
|
|
|
|
enquiries = regularEnquiries
|
|
|
|
|
} else {
|
|
|
|
|
searchResults, err := h.queries.SearchEnquiries(r.Context(), db.SearchEnquiriesParams{
|
|
|
|
|
CONCAT: query,
|
|
|
|
|
CONCAT_2: query,
|
|
|
|
|
CONCAT_3: query,
|
|
|
|
|
Limit: int32(limit + 1),
|
|
|
|
|
Offset: int32(offset),
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
hasMore = len(searchResults) > limit
|
|
|
|
|
if hasMore {
|
|
|
|
|
searchResults = searchResults[:limit]
|
|
|
|
|
}
|
|
|
|
|
enquiries = searchResults
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Enquiries": enquiries,
|
|
|
|
|
"Page": page,
|
|
|
|
|
"PrevPage": page - 1,
|
|
|
|
|
"NextPage": page + 1,
|
|
|
|
|
"HasMore": hasMore,
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
|
2025-06-24 03:32:28 -07:00
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
|
|
|
if err := h.tmpl.RenderPartial(w, "enquiries/table.html", "enquiry-table", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
2025-07-02 05:04:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Document page handlers
|
|
|
|
|
func (h *PageHandler) DocumentsIndex(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
page := 1
|
|
|
|
|
if p := r.URL.Query().Get("page"); p != "" {
|
|
|
|
|
if val, err := strconv.Atoi(p); err == nil && val > 0 {
|
|
|
|
|
page = val
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get document type filter
|
|
|
|
|
docType := r.URL.Query().Get("type")
|
|
|
|
|
|
2025-07-06 14:34:12 -07:00
|
|
|
limit := 20
|
|
|
|
|
offset := (page - 1) * limit
|
|
|
|
|
|
2025-07-02 05:04:36 -07:00
|
|
|
var documents interface{}
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
if docType != "" {
|
2025-07-06 14:34:12 -07:00
|
|
|
documents, err = h.queries.ListDocumentsByType(r.Context(), db.ListDocumentsByTypeParams{
|
|
|
|
|
Type: db.DocumentsType(docType),
|
|
|
|
|
Limit: int32(limit + 1),
|
|
|
|
|
Offset: int32(offset),
|
|
|
|
|
})
|
2025-07-02 05:04:36 -07:00
|
|
|
} else {
|
2025-07-06 14:34:12 -07:00
|
|
|
documents, err = h.queries.ListDocuments(r.Context(), db.ListDocumentsParams{
|
|
|
|
|
Limit: int32(limit + 1),
|
|
|
|
|
Offset: int32(offset),
|
|
|
|
|
})
|
2025-07-02 05:04:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get users list for display names (if needed)
|
|
|
|
|
users, err := h.queries.GetAllUsers(r.Context())
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Documents": documents,
|
|
|
|
|
"Users": users,
|
|
|
|
|
"Page": page,
|
|
|
|
|
"DocType": docType,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if this is an HTMX request
|
|
|
|
|
if r.Header.Get("HX-Request") == "true" {
|
|
|
|
|
if err := h.tmpl.RenderPartial(w, "documents/table.html", "document-table", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := h.tmpl.Render(w, "documents/index.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) DocumentsShow(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
|
id, err := strconv.Atoi(vars["id"])
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Invalid document ID", http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-06 14:34:12 -07:00
|
|
|
document, err := h.queries.GetDocumentWithUser(r.Context(), int32(id))
|
2025-07-02 05:04:36 -07:00
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Error fetching document %d: %v", id, err)
|
|
|
|
|
http.Error(w, "Document not found", http.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Document": document,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := h.tmpl.Render(w, "documents/show.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) DocumentsSearch(w http.ResponseWriter, r *http.Request) {
|
2025-07-06 14:34:12 -07:00
|
|
|
// query := r.URL.Query().Get("search") // TODO: Use when search is implemented
|
2025-07-02 05:04:36 -07:00
|
|
|
|
|
|
|
|
var documents interface{}
|
|
|
|
|
var err error
|
|
|
|
|
|
2025-07-06 14:34:12 -07:00
|
|
|
// For now, just return all documents until search is implemented
|
|
|
|
|
limit := 20
|
|
|
|
|
offset := 0
|
|
|
|
|
|
|
|
|
|
documents, err = h.queries.ListDocuments(r.Context(), db.ListDocumentsParams{
|
|
|
|
|
Limit: int32(limit),
|
|
|
|
|
Offset: int32(offset),
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
2025-07-02 05:04:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Documents": documents,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
|
|
|
if err := h.tmpl.RenderPartial(w, "documents/table.html", "document-table", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *PageHandler) DocumentsView(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
|
id, err := strconv.Atoi(vars["id"])
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Invalid document ID", http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-06 14:34:12 -07:00
|
|
|
document, err := h.queries.GetDocumentWithUser(r.Context(), int32(id))
|
2025-07-02 05:04:36 -07:00
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Document not found", http.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load line items for this document
|
|
|
|
|
lineItems, err := h.queries.ListLineItemsByDocument(r.Context(), int32(id))
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Error loading line items for document %d: %v", id, err)
|
|
|
|
|
// Don't fail the entire page if line items can't be loaded
|
|
|
|
|
lineItems = []db.LineItem{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Prepare data based on document type
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Document": document,
|
|
|
|
|
"DocType": string(document.Type),
|
|
|
|
|
"LineItems": lineItems,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add document type specific data
|
|
|
|
|
switch document.Type {
|
|
|
|
|
case db.DocumentsTypeQuote:
|
|
|
|
|
// For quotes, we might need to load enquiry data
|
|
|
|
|
if document.CmcReference != "" {
|
|
|
|
|
// The CmcReference for quotes is the enquiry title
|
|
|
|
|
data["EnquiryTitle"] = document.CmcReference
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case db.DocumentsTypeInvoice:
|
|
|
|
|
// For invoices, load job and customer data if needed
|
|
|
|
|
data["ShowPaymentButton"] = true
|
|
|
|
|
|
|
|
|
|
case db.DocumentsTypePurchaseOrder:
|
|
|
|
|
// For purchase orders, load principle data if needed
|
|
|
|
|
|
|
|
|
|
case db.DocumentsTypeOrderAck:
|
|
|
|
|
// For order acknowledgements, load job data if needed
|
|
|
|
|
|
|
|
|
|
case db.DocumentsTypePackingList:
|
|
|
|
|
// For packing lists, load job data if needed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Render the appropriate template
|
|
|
|
|
if err := h.tmpl.Render(w, "documents/view.html", data); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|