feat: dashboard working

This commit is contained in:
2025-12-10 12:47:11 -07:00
parent 39f484ee05
commit 1b894d7f12
31 changed files with 2159 additions and 587 deletions

View File

@@ -11,7 +11,7 @@ FlexSupport is a lightweight helpdesk and ticketing system specifically tailored
### Backend
- **Go 1.25** - Primary language
- **Chi (v5)** - HTTP router and middleware
- **Templ** - Type-safe Go templating (generates `*_templ.go` files)
- **Templ** - Type-safe Go templating (generates `*_templ.go` files) - See the url `https://templ.guide/llms.md` for llm instructions.
- **Shopify Integration** - Customer/order integration via `go-shopify` SDK
### Frontend

4
app.go
View File

@@ -9,7 +9,6 @@ import (
"strings"
cfg "flexsupport/internal/config"
"flexsupport/internal/handlers"
"flexsupport/internal/lib/logger"
"flexsupport/internal/router"
)
@@ -33,8 +32,7 @@ func App(ctx context.Context, stdout io.Writer, getenv func(string, string) stri
default:
log = slog.New(slog.NewJSONHandler(stdout, logOptions))
}
h := handlers.NewHandler()
r := router.NewRouter(h, log)
r := router.NewRouter(log)
fmt.Println("Starting server on :8080")
return http.ListenAndServe(":8080", r)

View File

@@ -1,12 +1,13 @@
package handlers
/**
* @Note: this is now unused and will be deleted
*/
import (
"fmt"
"log"
"net/http"
// "strconv"
"strings"
"time"
"flexsupport/internal/models"
@@ -24,18 +25,6 @@ func NewHandler() *Handler {
return &Handler{}
}
// // Dashboard renders the main dashboard view
// func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
// // TODO: Fetch real data from database
// fmt.Println("Rendering dashboard")
// page := pages.Dashboard(getMockTickets())
// err := layouts.BaseLayout(page).Render(r.Context(), w)
// if err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// return
// }
// }
// // ListTickets handles the ticket listing page
// func (h *Handler) ListTickets(w http.ResponseWriter, r *http.Request) {
// // TODO: Implement filtering based on query parameters
@@ -59,30 +48,6 @@ func NewHandler() *Handler {
// }
// }
func filterTicketsByStatus(tickets []models.Ticket, status string) []models.Ticket {
result := make([]models.Ticket, 0)
if status != "" {
for _, ticket := range tickets {
if ticket.Status.String() == status {
result = append(result, ticket)
}
}
}
return result
}
func filterTicketsBySearch(tickets []models.Ticket, search string) []models.Ticket {
result := make([]models.Ticket, 0)
if search != "" {
for _, ticket := range tickets {
if strings.Contains(ticket.CustomerName, search) || strings.Contains(ticket.IssueDescription, search) {
result = append(result, ticket)
}
}
}
return result
}
// CreateTicket handles ticket creation
func (h *Handler) CreateTicket(w http.ResponseWriter, r *http.Request) {
// TODO: Parse form data

View File

@@ -14,7 +14,7 @@ import (
// "flexsupport/ui/components/textarea"
// "flexsupport/ui/components/toast"
// "flexsupport/internal/middleware"
"flexsupport/ui/modules"
"flexsupport/ui/components/navbar"
)
templ BaseLayout(contents templ.Component) {
@@ -29,11 +29,36 @@ templ BaseLayout(contents templ.Component) {
<script defer src="/assets/js/alpine.min.js"></script>
@input.Script()
@label.Script()
<script nonce={ templ.GetNonce(ctx) }>
(function() {
// Get current theme preference (system, light, or dark)
function getThemePreference() {
return localStorage.getItem('themePreference') || 'system';
}
// Apply theme based on preference
const preference = getThemePreference();
let isDark = false;
if (preference === 'system') {
// Use system preference
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
} else {
// Use explicit preference
isDark = preference === 'dark';
}
// Apply theme immediately to prevent flash
if (isDark) {
document.documentElement.classList.add('dark');
}
})();
</script>
<title>Flex Support</title>
</head>
<body class="h-full" hx-ext="response-targets">
@modules.Navbar("dickhead")
<main class="container">
<body class="h-full transition-colors duration-300" hx-ext="response-targets">
@navbar.Navbar("dickhead")
<main class="container-wrapper">
@contents
</main>
</body>

View File

@@ -22,7 +22,7 @@ import (
// "flexsupport/ui/components/textarea"
// "flexsupport/ui/components/toast"
// "flexsupport/internal/middleware"
"flexsupport/ui/modules"
"flexsupport/ui/components/navbar"
)
func BaseLayout(contents templ.Component) templ.Component {
@@ -58,15 +58,28 @@ func BaseLayout(contents templ.Component) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<title>Flex Support</title></head><body class=\"h-full\" hx-ext=\"response-targets\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<script nonce=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = modules.Navbar("dickhead").Render(ctx, templ_7745c5c3_Buffer)
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(templ.GetNonce(ctx))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/layout/base.templ`, Line: 32, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<main class=\"container\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">\n\t\t\t\t(function() {\n\t\t\t\t\t// Get current theme preference (system, light, or dark)\n\t\t\t\t\tfunction getThemePreference() {\n\t\t\t\t\t\treturn localStorage.getItem('themePreference') || 'system';\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// Apply theme based on preference\n\t\t\t\t\tconst preference = getThemePreference();\n\t\t\t\t\tlet isDark = false;\n\t\t\t\t\t\n\t\t\t\t\tif (preference === 'system') {\n\t\t\t\t\t\t// Use system preference\n\t\t\t\t\t\tisDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Use explicit preference\n\t\t\t\t\t\tisDark = preference === 'dark';\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// Apply theme immediately to prevent flash\n\t\t\t\t\tif (isDark) {\n\t\t\t\t\t\tdocument.documentElement.classList.add('dark');\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t</script><title>Flex Support</title></head><body class=\"h-full transition-colors duration-300\" hx-ext=\"response-targets\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = navbar.Navbar("dickhead").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<main class=\"container-wrapper\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -74,7 +87,7 @@ func BaseLayout(contents templ.Component) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</main></body></html>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</main></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -6,6 +6,6 @@ import (
"github.com/a-h/templ"
)
func Handler(content templ.Component, options ...func(*templ.ComponentHandler)) http.Handler {
return templ.Handler(BaseLayout(content), options...)
func Handler(content templ.Component) http.Handler {
return templ.Handler(BaseLayout(content))
}

View File

@@ -4,13 +4,13 @@ import (
"log/slog"
"net/http"
"flexsupport/internal/handlers"
mw "flexsupport/internal/middleware"
"flexsupport/static"
// "net/http"
"flexsupport/internal/routes/api"
"flexsupport/internal/routes/dashboard"
// "flexsupport/internal/routes/tickets"
"flexsupport/internal/routes/tickets"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
@@ -18,11 +18,10 @@ import (
var AppDev string = "development"
func NewRouter(h *handlers.Handler, log *slog.Logger) *chi.Mux {
func NewRouter(log *slog.Logger) *chi.Mux {
r := chi.NewMux()
// Dashboard
dbHandler := dashboard.NewHandler(log)
r.Handle("/assets/*",
disableCacheInDevMode(
http.StripPrefix("/assets/",
@@ -38,28 +37,10 @@ func NewRouter(h *handlers.Handler, log *slog.Logger) *chi.Mux {
mw.Logging(log),
mw.TextHTMLMiddleware,
)
r.Get("/", dbHandler.Get)
r.Route("/tickets", func(r chi.Router) {
// r.Get("/", h.ListTickets)
// r.Get("/new", h.NewTicketForm)
r.Post("/", h.CreateTicket)
r.Get("/search", h.SearchTickets)
// r.Get("/{id}", h.ViewTicket)
// r.Get("/{id}/edit", h.EditTicketForm)
r.Post("/{id}", h.UpdateTicket)
// Ticket actions
r.Post("/{id}/status", h.UpdateTicketStatus)
r.Post("/{id}/parts", h.AddPart)
r.Delete("/{id}/parts/{partId}", h.DeletePart)
r.Post("/{id}/notes", h.AddNote)
})
})
// API endpoints (for htmx)
r.Route("/api", func(r chi.Router) {
r.Get("/stats/open", h.GetOpenTicketsCount)
dashboard.Mount(r, dashboard.NewHandler(log, dashboard.NewService(log)))
tickets.Mount(r, tickets.NewHandler(log, tickets.NewService(log)))
})
api.Mount(r, api.NewHandler(log, api.NewService(log)))
return r
}

View File

@@ -0,0 +1,48 @@
package api
import (
"fmt"
"log/slog"
"net/http"
"github.com/go-chi/chi/v5"
)
type (
Handler interface {
GetOpenTicketCount(w http.ResponseWriter, r *http.Request)
}
handler struct {
log *slog.Logger
service Service
}
)
func NewHandler(log *slog.Logger, svc Service) Handler {
return &handler{
log: log.With("Handler", "api"),
service: svc,
}
}
func Mount(r chi.Router, h Handler) {
r.Route("/api", func(r chi.Router) {
r.Route("/stats", func(r chi.Router) {
r.Get("/open", h.GetOpenTicketCount)
})
})
}
func (h *handler) GetOpenTicketCount(w http.ResponseWriter, r *http.Request) {
// TODO: Query database
count, err := h.service.OpenTicketsCount(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
countString := fmt.Sprintf("%d", count)
fmt.Fprintf(w, "%s", countString)
}

View File

@@ -0,0 +1,25 @@
package api
import (
"context"
"log/slog"
)
type (
Service interface {
OpenTicketsCount(ctx context.Context) (int, error)
}
service struct {
log *slog.Logger
}
)
func NewService(log *slog.Logger) Service {
return &service{
log: log.With("Service", "api"),
}
}
func (s service) OpenTicketsCount(ctx context.Context) (int, error) {
return 2, nil
}

View File

@@ -2,17 +2,17 @@ package dashboard
import (
"flexsupport/internal/models"
"flexsupport/internal/utils"
"flexsupport/ui/components/card"
"fmt"
"flexsupport/ui/partials/tables"
"flexsupport/ui/partials/search"
)
templ Dashboard(tickets []models.Ticket) {
<div class="px-4 py-6 sm:px-0">
<div class="container px-4 py-6 sm:px-0">
<!-- Page Header -->
<div class="mb-6">
<h2 class="text-2xl font-bold text-gray-900">Ticket Dashboard</h2>
<p class="mt-1 text-sm text-gray-600">Manage and track repair tickets</p>
<h2 class="text-2xl font-bold">Ticket Dashboard</h2>
<p class="mt-1 text-sm ">Manage and track repair tickets</p>
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4 mb-8">
@@ -33,9 +33,9 @@ templ Dashboard(tickets []models.Ticket) {
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Open Tickets</dt>
<dt class="text-sm font-medium truncate">Open Tickets</dt>
<dd
class="text-2xl font-semibold text-gray-900"
class="text-2xl font-semibold "
hx-get="/api/stats/open"
hx-trigger="load, every 30s"
hx-target="#open-tickets"
@@ -52,7 +52,7 @@ templ Dashboard(tickets []models.Ticket) {
<div class="flex items-center">
<div class="shrink-0">
<div class="rounded-md bg-yellow-500 p-3">
<svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="h-6 w-6 text-primary-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
@@ -64,9 +64,9 @@ templ Dashboard(tickets []models.Ticket) {
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">In Progress</dt>
<dt class="text-sm font-medium truncate">In Progress</dt>
<dd
class="text-2xl font-semibold text-gray-900"
class="text-2xl font-semibold "
hx-get="/api/stats/inprogress"
hx-trigger="load, every 30s"
hx-target="#inprogress-tickets"
@@ -95,9 +95,9 @@ templ Dashboard(tickets []models.Ticket) {
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Overdue</dt>
<dt class="text-sm font-medium truncate">Overdue</dt>
<dd
class="text-2xl font-semibold text-gray-900"
class="text-2xl font-semibold "
hx-get="/api/stats/overdue"
hx-trigger="load, every 30s"
hx-target="#overdue-tickets"
@@ -140,135 +140,16 @@ templ Dashboard(tickets []models.Ticket) {
@card.Card() {
@card.Content() {
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<span class="htmx-indicator">
<img src="/assets/icons/loader.svg" class="size-4 animate-spin" alt="Loading..."/> Loading...
</span>
<div class="flex-1">
<input
type="search"
name="search"
placeholder="Search tickets..."
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
hx-get="/"
hx-trigger="keyup changed delay:300ms"
hx-target="#ticket-list"
hx-indicator=".htmx-indicator"
/>
</div>
<div class="flex gap-2">
<select
name="status"
class="block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md"
hx-get="/"
hx-trigger="change"
hx-target="#ticket-list"
hx-swap="innerHTML"
hx-include="[name='search']"
>
<option value="">All Statuses</option>
<option value="new">New</option>
<option value="in_progress">In Progress</option>
<option value="waiting_parts">Waiting for Parts</option>
<option value="ready">Ready for Pickup</option>
<option value="completed">Completed</option>
</select>
<a
href="/tickets/new"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
New Ticket
</a>
</div>
@search.SearchTickets("", "")
<a
href="/tickets/new"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
New Ticket
</a>
</div>
}
}
@TicketsTable(tickets)
@tables.TicketsTable(tickets)
</div>
}
templ TicketsTable(tickets []models.Ticket) {
<div
class="bg-white shadow rounded-lg overflow-hidden"
>
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Ticket #
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Customer
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Item
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Status
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Assigned To
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Due Date
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<!-- <tbody id="ticket-list" hx-get="/tickets" hx-trigger="load, every 30s" hx-select="#ticket-list" hx-swap="innerHTML" class="bg-white divide-y divide-gray-200"> -->
@templ.Fragment("ticket-list") {
<tbody
class="bg-white divide-y divide-gray-200"
id="ticket-list"
hx-get="/?status={{ .status }}&search={{ .search }}&template=ticket-list"
hx-trigger="load, every 30s"
hx-target="#ticket-list"
hx-swap="outerHTML"
>
for _, ticket := range tickets {
{{ ticketUrl := fmt.Sprintf("/tickets/%d", ticket.ID) }}
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
<a href={ ticketUrl } class="text-blue-600 hover:text-blue-900">{ ticket.ID }</a>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">{ ticket.CustomerName }</div>
<div class="text-sm text-gray-500">{ ticket.CustomerEmail }</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{ ticket.ItemType }
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class={ utils.TwMerge("px-2 inline-flex text-xs leading-5 font-semibold rounded-full", ticket.StatusClass()) }
>
{ ticket.StatusDisplay() }
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
if ticket.AssignedTo != "" {
{ ticket.AssignedTo }
} else {
<span class="text-gray-400">Unassigned</span>
}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{ ticket.DueDate.Format("2006-01-02") }
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
<a href={ fmt.Sprintf("/tickets/%d/edit", ticket.ID) } class="text-blue-600 hover:text-blue-900">Edit</a>
<a href={ ticketUrl } class="text-gray-600 hover:text-gray-900">View</a>
</td>
</tr>
}
</tbody>
}
</table>
</div>
}
// templ TicketRows(tickets []models.Ticket) {
// }
// }

View File

@@ -10,9 +10,9 @@ import templruntime "github.com/a-h/templ/runtime"
import (
"flexsupport/internal/models"
"flexsupport/internal/utils"
"flexsupport/ui/components/card"
"fmt"
"flexsupport/ui/partials/search"
"flexsupport/ui/partials/tables"
)
func Dashboard(tickets []models.Ticket) templ.Component {
@@ -36,7 +36,7 @@ func Dashboard(tickets []models.Ticket) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"px-4 py-6 sm:px-0\"><!-- Page Header --><div class=\"mb-6\"><h2 class=\"text-2xl font-bold text-gray-900\">Ticket Dashboard</h2><p class=\"mt-1 text-sm text-gray-600\">Manage and track repair tickets</p></div><!-- Stats Cards --><div class=\"grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4 mb-8\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"container px-4 py-6 sm:px-0\"><!-- Page Header --><div class=\"mb-6\"><h2 class=\"text-2xl font-bold\">Ticket Dashboard</h2><p class=\"mt-1 text-sm \">Manage and track repair tickets</p></div><!-- Stats Cards --><div class=\"grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4 mb-8\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -64,7 +64,7 @@ func Dashboard(tickets []models.Ticket) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"flex items-center\"><div class=\"flex-shrink-0\"><div class=\"rounded-md bg-blue-500 p-3\"><svg class=\"h-6 w-6 text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"></path></svg></div></div><div class=\"ml-5 w-0 flex-1\"><dl><dt class=\"text-sm font-medium text-gray-500 truncate\">Open Tickets</dt><dd class=\"text-2xl font-semibold text-gray-900\" hx-get=\"/api/stats/open\" hx-trigger=\"load, every 30s\" hx-target=\"#open-tickets\"><div id=\"open-tickets\" readonly></div></dd></dl></div></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"flex items-center\"><div class=\"flex-shrink-0\"><div class=\"rounded-md bg-blue-500 p-3\"><svg class=\"h-6 w-6 text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"></path></svg></div></div><div class=\"ml-5 w-0 flex-1\"><dl><dt class=\"text-sm font-medium truncate\">Open Tickets</dt><dd class=\"text-2xl font-semibold \" hx-get=\"/api/stats/open\" hx-trigger=\"load, every 30s\" hx-target=\"#open-tickets\"><div id=\"open-tickets\" readonly></div></dd></dl></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -104,7 +104,7 @@ func Dashboard(tickets []models.Ticket) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"flex items-center\"><div class=\"shrink-0\"><div class=\"rounded-md bg-yellow-500 p-3\"><svg class=\"h-6 w-6 text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"></path></svg></div></div><div class=\"ml-5 w-0 flex-1\"><dl><dt class=\"text-sm font-medium text-gray-500 truncate\">In Progress</dt><dd class=\"text-2xl font-semibold text-gray-900\" hx-get=\"/api/stats/inprogress\" hx-trigger=\"load, every 30s\" hx-target=\"#inprogress-tickets\"><div id=\"inprogress-tickets\" readonly></div></dd></dl></div></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"flex items-center\"><div class=\"shrink-0\"><div class=\"rounded-md bg-yellow-500 p-3\"><svg class=\"h-6 w-6 text-primary-foreground\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"></path></svg></div></div><div class=\"ml-5 w-0 flex-1\"><dl><dt class=\"text-sm font-medium truncate\">In Progress</dt><dd class=\"text-2xl font-semibold \" hx-get=\"/api/stats/inprogress\" hx-trigger=\"load, every 30s\" hx-target=\"#inprogress-tickets\"><div id=\"inprogress-tickets\" readonly></div></dd></dl></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -144,7 +144,7 @@ func Dashboard(tickets []models.Ticket) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"flex items-center\"><div class=\"flex-shrink-0\"><div class=\"rounded-md bg-red-500 p-3\"><svg class=\"h-6 w-6 text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"></path></svg></div></div><div class=\"ml-5 w-0 flex-1\"><dl><dt class=\"text-sm font-medium text-gray-500 truncate\">Overdue</dt><dd class=\"text-2xl font-semibold text-gray-900\" hx-get=\"/api/stats/overdue\" hx-trigger=\"load, every 30s\" hx-target=\"#overdue-tickets\"><div id=\"overdue-tickets\" readonly></div></dd></dl></div></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"flex items-center\"><div class=\"flex-shrink-0\"><div class=\"rounded-md bg-red-500 p-3\"><svg class=\"h-6 w-6 text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"></path></svg></div></div><div class=\"ml-5 w-0 flex-1\"><dl><dt class=\"text-sm font-medium truncate\">Overdue</dt><dd class=\"text-2xl font-semibold \" hx-get=\"/api/stats/overdue\" hx-trigger=\"load, every 30s\" hx-target=\"#overdue-tickets\"><div id=\"overdue-tickets\" readonly></div></dd></dl></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -228,7 +228,15 @@ func Dashboard(tickets []models.Ticket) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4\"><span class=\"htmx-indicator\"><img src=\"/assets/icons/loader.svg\" class=\"size-4 animate-spin\" alt=\"Loading...\"> Loading...</span><div class=\"flex-1\"><input type=\"search\" name=\"search\" placeholder=\"Search tickets...\" class=\"shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md\" hx-get=\"/\" hx-trigger=\"keyup changed delay:300ms\" hx-target=\"#ticket-list\" hx-indicator=\".htmx-indicator\"></div><div class=\"flex gap-2\"><select name=\"status\" class=\"block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md\" hx-get=\"/\" hx-trigger=\"change\" hx-target=\"#ticket-list\" hx-swap=\"innerHTML\" hx-include=\"[name='search']\"><option value=\"\">All Statuses</option> <option value=\"new\">New</option> <option value=\"in_progress\">In Progress</option> <option value=\"waiting_parts\">Waiting for Parts</option> <option value=\"ready\">Ready for Pickup</option> <option value=\"completed\">Completed</option></select> <a href=\"/tickets/new\" class=\"inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\">New Ticket</a></div></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = search.SearchTickets("", "").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<a href=\"/tickets/new\" class=\"inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\">New Ticket</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -244,11 +252,11 @@ func Dashboard(tickets []models.Ticket) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = TicketsTable(tickets).Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Err = tables.TicketsTable(tickets).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -256,233 +264,4 @@ func Dashboard(tickets []models.Ticket) templ.Component {
})
}
func TicketsTable(tickets []models.Ticket) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var12 := templ.GetChildren(ctx)
if templ_7745c5c3_Var12 == nil {
templ_7745c5c3_Var12 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<div class=\"bg-white shadow rounded-lg overflow-hidden\"><table class=\"min-w-full divide-y divide-gray-200\"><thead class=\"bg-gray-50\"><tr><th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">Ticket #</th><th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">Customer</th><th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">Item</th><th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">Status</th><th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">Assigned To</th><th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">Due Date</th><th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">Actions</th></tr></thead><!-- <tbody id=\"ticket-list\" hx-get=\"/tickets\" hx-trigger=\"load, every 30s\" hx-select=\"#ticket-list\" hx-swap=\"innerHTML\" class=\"bg-white divide-y divide-gray-200\"> -->")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var13 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<tbody class=\"bg-white divide-y divide-gray-200\" id=\"ticket-list\" hx-get=\"/?status={{ .status }}&search={{ .search }}&template=ticket-list\" hx-trigger=\"load, every 30s\" hx-target=\"#ticket-list\" hx-swap=\"outerHTML\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, ticket := range tickets {
ticketUrl := fmt.Sprintf("/tickets/%d", ticket.ID)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<tr class=\"hover:bg-gray-50\"><td class=\"px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 templ.SafeURL
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinURLErrs(ticketUrl)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/routes/dashboard/dashboard.templ`, Line: 233, Col: 27}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" class=\"text-blue-600 hover:text-blue-900\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(ticket.ID)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/routes/dashboard/dashboard.templ`, Line: 233, Col: 83}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</a></td><td class=\"px-6 py-4 whitespace-nowrap\"><div class=\"text-sm font-medium text-gray-900\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(ticket.CustomerName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/routes/dashboard/dashboard.templ`, Line: 236, Col: 76}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div><div class=\"text-sm text-gray-500\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(ticket.CustomerEmail)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/routes/dashboard/dashboard.templ`, Line: 237, Col: 65}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div></td><td class=\"px-6 py-4 whitespace-nowrap\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(ticket.ItemType)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/routes/dashboard/dashboard.templ`, Line: 240, Col: 25}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</td><td class=\"px-6 py-4 whitespace-nowrap\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 = []any{utils.TwMerge("px-2 inline-flex text-xs leading-5 font-semibold rounded-full", ticket.StatusClass())}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var19...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<span class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var19).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/routes/dashboard/dashboard.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(ticket.StatusDisplay())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/routes/dashboard/dashboard.templ`, Line: 246, Col: 33}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</span></td><td class=\"px-6 py-4 whitespace-nowrap text-sm text-gray-500\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if ticket.AssignedTo != "" {
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(ticket.AssignedTo)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/routes/dashboard/dashboard.templ`, Line: 251, Col: 28}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<span class=\"text-gray-400\">Unassigned</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</td><td class=\"px-6 py-4 whitespace-nowrap text-sm text-gray-500\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(ticket.DueDate.Format("2006-01-02"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/routes/dashboard/dashboard.templ`, Line: 257, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</td><td class=\"px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var24 templ.SafeURL
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinURLErrs(fmt.Sprintf("/tickets/%d/edit", ticket.ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/routes/dashboard/dashboard.templ`, Line: 260, Col: 60}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" class=\"text-blue-600 hover:text-blue-900\">Edit</a> <a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var25 templ.SafeURL
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinURLErrs(ticketUrl)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/routes/dashboard/dashboard.templ`, Line: 261, Col: 27}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\" class=\"text-gray-600 hover:text-gray-900\">View</a></td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</tbody>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = templ.Fragment("ticket-list").Render(templ.WithChildren(ctx, templ_7745c5c3_Var13), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</table></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// templ TicketRows(tickets []models.Ticket) {
// }
// }
var _ = templruntime.GeneratedTemplate

View File

@@ -4,85 +4,45 @@ import (
"log/slog"
"net/http"
"strings"
"time"
"flexsupport/internal/layout"
"flexsupport/internal/models"
"github.com/a-h/templ"
"github.com/go-chi/chi/v5"
)
type Handler struct {
log *slog.Logger
}
type (
Handler interface {
Get(w http.ResponseWriter, r *http.Request)
}
handler struct {
log *slog.Logger
service Service
}
)
func NewHandler(log *slog.Logger) *Handler {
return &Handler{
log: log,
func NewHandler(log *slog.Logger, svc Service) Handler {
return &handler{
log: log,
service: svc,
}
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
h.Get(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
func Mount(r chi.Router, h Handler) {
r.Get("/", h.Get)
}
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
status := r.URL.Query().Get("status")
search := r.URL.Query().Get("search")
template := r.URL.Query().Get("template")
// TODO: Fetch real data from database
tickets := getMockTickets()
if status != "" {
tickets = filterTicketsByStatus(tickets, status)
}
if search != "" {
tickets = filterTicketsBySearch(tickets, search)
func (h handler) Get(w http.ResponseWriter, r *http.Request) {
tickets, err := h.service.List(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var opts []func(*templ.ComponentHandler)
if template != "" {
opts = append(opts, templ.WithFragments(template))
}
v := layout.Handler(Dashboard(tickets), opts...)
v := layout.Handler(Dashboard(tickets))
v.ServeHTTP(w, r)
}
func getMockTickets() []models.Ticket {
return []models.Ticket{
{
ID: 1001,
Status: "new",
Priority: "high",
CustomerName: "John Doe",
CustomerPhone: "(555) 123-4567",
ItemType: models.Bag,
ItemModel: "Backpack",
IssueDescription: "Broken strap, needs replacement",
AssignedTo: "Mike Tech",
DueDate: time.Now().Add(48 * time.Hour),
},
{
ID: 1002,
Status: "in_progress",
Priority: "normal",
CustomerName: "Jane Smith",
CustomerPhone: "(555) 987-6543",
ItemType: models.Boot,
ItemModel: "Keen",
IssueDescription: "Sole needs replaced",
AssignedTo: "Sarah Tech",
DueDate: time.Now().Add(72 * time.Hour),
},
}
}
func filterTicketsByStatus(tickets []models.Ticket, status string) []models.Ticket {
result := make([]models.Ticket, 0)
if status != "" {

View File

@@ -0,0 +1,58 @@
package dashboard
import (
"context"
"log/slog"
"time"
"flexsupport/internal/models"
)
type (
Service interface {
List(ctx context.Context) ([]models.Ticket, error)
}
service struct {
log *slog.Logger
}
)
func NewService(log *slog.Logger) Service {
return &service{
log: log.With("Dashboard Service"),
}
}
func (s service) List(context.Context) ([]models.Ticket, error) {
return getMockTickets(), nil
}
func getMockTickets() []models.Ticket {
return []models.Ticket{
{
ID: 1001,
Status: "new",
Priority: "high",
CustomerName: "John Doe",
CustomerPhone: "(555) 123-4567",
ItemType: models.Bag,
ItemModel: "Backpack",
IssueDescription: "Broken strap, needs replacement",
AssignedTo: "Mike Tech",
DueDate: time.Now().Add(48 * time.Hour),
},
{
ID: 1002,
Status: "in_progress",
Priority: "normal",
CustomerName: "Jane Smith",
CustomerPhone: "(555) 987-6543",
ItemType: models.Boot,
ItemModel: "Keen",
IssueDescription: "Sole needs replaced",
AssignedTo: "Sarah Tech",
DueDate: time.Now().Add(72 * time.Hour),
},
}
}

View File

@@ -0,0 +1,106 @@
package tickets
import (
"log/slog"
"net/http"
"strconv"
"flexsupport/internal/layout"
"flexsupport/internal/models"
"flexsupport/ui/partials/rows"
"github.com/go-chi/chi/v5"
)
type (
Handler interface {
Search(w http.ResponseWriter, r *http.Request)
Get(w http.ResponseWriter, r *http.Request)
New(w http.ResponseWriter, r *http.Request)
}
handler struct {
log *slog.Logger
service Service
}
)
func NewHandler(log *slog.Logger, svc Service) Handler {
return &handler{
log: log.With("Tickets Handler"),
service: svc,
}
}
func Mount(r chi.Router, h Handler) {
r.Route("/tickets", func(r chi.Router) {
r.Get("/", h.Search)
r.Route("/{ticketId}", func(r chi.Router) {
r.Get("/", h.Get)
})
r.Get("/new", h.New)
})
}
func (h handler) Search(w http.ResponseWriter, r *http.Request) {
status := r.URL.Query().Get("status")
search := r.URL.Query().Get("search")
tickets, err := h.service.Search(r.Context(), search, status)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
switch isHTMX(r) {
case true:
err = rows.TicketRows(tickets).Render(r.Context(), w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
default:
err = layout.BaseLayout(TicketsPage(tickets, search, status)).Render(r.Context(), w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
func (h handler) Get(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "ticketId")
ticketID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
http.Error(w, "Invalid ticket ID", http.StatusBadRequest)
return
}
ticket, err := h.service.Get(r.Context(), ticketID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
page := TicketPage(ticket)
err = layout.BaseLayout(page).Render(r.Context(), w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (h handler) New(w http.ResponseWriter, r *http.Request) {
page := TicketForm(models.Ticket{})
err := layout.BaseLayout(page).Render(r.Context(), w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func isHTMX(r *http.Request) bool {
// Check for "HX-Request" header
if r.Header.Get("HX-Request") != "" {
return true
}
return false
}

View File

@@ -0,0 +1,123 @@
package tickets
import (
"context"
"errors"
"log/slog"
"strings"
"time"
"flexsupport/internal/models"
)
type (
Service interface {
Search(ctx context.Context, search, status string) ([]models.Ticket, error)
Get(ctx context.Context, id int64) (models.Ticket, error)
}
service struct {
log *slog.Logger
}
)
func NewService(log *slog.Logger) Service {
return &service{
log: log.With("Service", "Tickets"),
}
}
func (s service) Search(ctx context.Context, search, status string) ([]models.Ticket, error) {
s.log.Debug("Searching for tickets", "search", search, "status", status)
tickets := getMockTickets()
if status != "" {
tickets = filterTicketsByStatus(tickets, status)
}
if search != "" {
tickets = filterTicketsBySearch(tickets, search)
}
return tickets, nil
}
func (s service) Get(ctx context.Context, id int64) (models.Ticket, error) {
tickets := getMockTickets()
for _, ticket := range tickets {
if ticket.ID == id {
return ticket, nil
}
}
return models.Ticket{}, errors.New("ticket not found")
}
/**
* Temporary mock data and filtering
* TODO: Fetch real data from database
*/
func getMockTickets() []models.Ticket {
return []models.Ticket{
{
ID: 1001,
Status: "new",
Priority: "high",
CustomerName: "John Doe",
CustomerPhone: "(555) 123-4567",
ItemType: models.Bag,
ItemModel: "Backpack",
IssueDescription: "Broken strap, needs replacement",
AssignedTo: "Mike Tech",
DueDate: time.Now().Add(48 * time.Hour),
},
{
ID: 1002,
Status: "in_progress",
Priority: "normal",
CustomerName: "Jane Smith",
CustomerPhone: "(555) 987-6543",
ItemType: models.Boot,
ItemModel: "Keen",
IssueDescription: "Sole needs replaced",
AssignedTo: "Sarah Tech",
DueDate: time.Now().Add(72 * time.Hour),
},
}
}
func getMockTicket(id int64) models.Ticket {
return models.Ticket{
ID: id,
Status: "in_progress",
Priority: "high",
CustomerName: "John Doe",
CustomerPhone: "(555) 123-4567",
ItemType: models.Bag,
ItemModel: "Backpack",
IssueDescription: "Broken strap, needs replacement",
AssignedTo: "Mike Tech",
DueDate: time.Now().Add(48 * time.Hour),
}
}
func filterTicketsByStatus(tickets []models.Ticket, status string) []models.Ticket {
result := make([]models.Ticket, 0)
if status != "" {
for _, ticket := range tickets {
if ticket.Status.String() == status {
result = append(result, ticket)
}
}
}
return result
}
func filterTicketsBySearch(tickets []models.Ticket, search string) []models.Ticket {
result := make([]models.Ticket, 0)
if search != "" {
for _, ticket := range tickets {
if strings.Contains(ticket.CustomerName, search) || strings.Contains(ticket.IssueDescription, search) {
result = append(result, ticket)
}
}
}
return result
}

View File

@@ -1,22 +0,0 @@
package tickets
import (
"log/slog"
"net/http"
"flexsupport/internal/layout"
"flexsupport/internal/models"
)
type Handler struct {
log *slog.Logger
}
func (h *Handler) NewTicketForm(w http.ResponseWriter, r *http.Request) {
page := TicketForm(models.Ticket{})
err := layout.BaseLayout(page).Render(r.Context(), w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

View File

@@ -0,0 +1,155 @@
package tickets
import (
"flexsupport/internal/models"
"flexsupport/ui/partials/tables"
"flexsupport/ui/partials/search"
"flexsupport/ui/components/card"
)
templ TicketsPage(tickets []models.Ticket, term, status string) {
<div class="px-4 py-6 sm:px-0">
<!-- Page Header -->
<div class="mb-6">
<h2 class="text-2xl font-bold text-gray-900">Ticket Dashboard</h2>
<p class="mt-1 text-sm text-gray-600">Manage and track repair tickets</p>
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4 mb-8">
@card.Card() {
@card.Content() {
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="rounded-md bg-blue-500 p-3">
<svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Open Tickets</dt>
<dd
class="text-2xl font-semibold text-gray-900"
hx-get="/api/stats/open"
hx-trigger="load, every 30s"
hx-target="#open-tickets"
>
<div id="open-tickets" readonly></div>
</dd>
</dl>
</div>
</div>
}
}
@card.Card() {
@card.Content() {
<div class="flex items-center">
<div class="shrink-0">
<div class="rounded-md bg-yellow-500 p-3">
<svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">In Progress</dt>
<dd
class="text-2xl font-semibold text-gray-900"
hx-get="/api/stats/inprogress"
hx-trigger="load, every 30s"
hx-target="#inprogress-tickets"
>
<div id="inprogress-tickets" readonly></div>
</dd>
</dl>
</div>
</div>
}
}
@card.Card() {
@card.Content() {
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="rounded-md bg-red-500 p-3">
<svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Overdue</dt>
<dd
class="text-2xl font-semibold text-gray-900"
hx-get="/api/stats/overdue"
hx-trigger="load, every 30s"
hx-target="#overdue-tickets"
>
<div id="overdue-tickets" readonly></div>
</dd>
</dl>
</div>
</div>
}
}
@card.Card() {
@card.Content() {
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="rounded-md bg-green-500 p-3">
<svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Completed Today</dt>
<dd
class="text-2xl font-semibold text-gray-900"
hx-get="/api/stats/completed"
hx-trigger="load, every 30s"
hx-target="#completed-today"
>
<div id="completed-today" readonly></div>
</dd>
</dl>
</div>
</div>
}
}
</div>
<!-- Filters and Search -->
@card.Card() {
@card.Content() {
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
@search.SearchTickets(term, status)
<a
href="/tickets/new"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
New Ticket
</a>
</div>
}
}
@tables.TicketsTable(tickets)
</div>
}

View File

@@ -0,0 +1,267 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.960
package tickets
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"flexsupport/internal/models"
"flexsupport/ui/components/card"
"flexsupport/ui/partials/search"
"flexsupport/ui/partials/tables"
)
func TicketsPage(tickets []models.Ticket, term, status string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"px-4 py-6 sm:px-0\"><!-- Page Header --><div class=\"mb-6\"><h2 class=\"text-2xl font-bold text-gray-900\">Ticket Dashboard</h2><p class=\"mt-1 text-sm text-gray-600\">Manage and track repair tickets</p></div><!-- Stats Cards --><div class=\"grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4 mb-8\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"flex items-center\"><div class=\"flex-shrink-0\"><div class=\"rounded-md bg-blue-500 p-3\"><svg class=\"h-6 w-6 text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"></path></svg></div></div><div class=\"ml-5 w-0 flex-1\"><dl><dt class=\"text-sm font-medium text-gray-500 truncate\">Open Tickets</dt><dd class=\"text-2xl font-semibold text-gray-900\" hx-get=\"/api/stats/open\" hx-trigger=\"load, every 30s\" hx-target=\"#open-tickets\"><div id=\"open-tickets\" readonly></div></dd></dl></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = card.Content().Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = card.Card().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var4 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"flex items-center\"><div class=\"shrink-0\"><div class=\"rounded-md bg-yellow-500 p-3\"><svg class=\"h-6 w-6 text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"></path></svg></div></div><div class=\"ml-5 w-0 flex-1\"><dl><dt class=\"text-sm font-medium text-gray-500 truncate\">In Progress</dt><dd class=\"text-2xl font-semibold text-gray-900\" hx-get=\"/api/stats/inprogress\" hx-trigger=\"load, every 30s\" hx-target=\"#inprogress-tickets\"><div id=\"inprogress-tickets\" readonly></div></dd></dl></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = card.Content().Render(templ.WithChildren(ctx, templ_7745c5c3_Var5), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = card.Card().Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var6 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var7 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"flex items-center\"><div class=\"flex-shrink-0\"><div class=\"rounded-md bg-red-500 p-3\"><svg class=\"h-6 w-6 text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"></path></svg></div></div><div class=\"ml-5 w-0 flex-1\"><dl><dt class=\"text-sm font-medium text-gray-500 truncate\">Overdue</dt><dd class=\"text-2xl font-semibold text-gray-900\" hx-get=\"/api/stats/overdue\" hx-trigger=\"load, every 30s\" hx-target=\"#overdue-tickets\"><div id=\"overdue-tickets\" readonly></div></dd></dl></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = card.Content().Render(templ.WithChildren(ctx, templ_7745c5c3_Var7), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = card.Card().Render(templ.WithChildren(ctx, templ_7745c5c3_Var6), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var8 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var9 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"flex items-center\"><div class=\"flex-shrink-0\"><div class=\"rounded-md bg-green-500 p-3\"><svg class=\"h-6 w-6 text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 13l4 4L19 7\"></path></svg></div></div><div class=\"ml-5 w-0 flex-1\"><dl><dt class=\"text-sm font-medium text-gray-500 truncate\">Completed Today</dt><dd class=\"text-2xl font-semibold text-gray-900\" hx-get=\"/api/stats/completed\" hx-trigger=\"load, every 30s\" hx-target=\"#completed-today\"><div id=\"completed-today\" readonly></div></dd></dl></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = card.Content().Render(templ.WithChildren(ctx, templ_7745c5c3_Var9), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = card.Card().Render(templ.WithChildren(ctx, templ_7745c5c3_Var8), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div><!-- Filters and Search -->")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var10 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var11 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = search.SearchTickets(term, status).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<a href=\"/tickets/new\" class=\"inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\">New Ticket</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = card.Content().Render(templ.WithChildren(ctx, templ_7745c5c3_Var11), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = card.Card().Render(templ.WithChildren(ctx, templ_7745c5c3_Var10), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = tables.TicketsTable(tickets).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -75,7 +75,7 @@
@layer base {
* {
@apply border-border;
@apply border-border outline-ring/50;
}
/* ::selection { */
/* @apply bg-selection text-selection-foreground; */
@@ -85,16 +85,16 @@
}
}
/* Smooth transitions for htmx swaps */
/* .htmx-swapping { */
/* opacity: 0; */
/* transition: opacity 200ms ease-out; */
/* } */
Smooth transitions for htmx swaps
.htmx-swapping {
opacity: 0;
transition: opacity 200ms ease-out;
}
/* Loading indicator for htmx requests */
/* .htmx-request .htmx-indicator { */
/* display: inline-block; */
/* } */
Loading indicator for htmx requests
.htmx-request .htmx-indicator {
display: inline-block;
}
/* .htmx-indicator { */
/* display: none; */
@@ -113,10 +113,41 @@
}
@layer utilities {
.step {
counter-increment: step;
}
.step:before {
@apply md:absolute w-8 h-8 md:w-9 md:h-9 bg-muted rounded-full font-mono font-medium text-center text-base inline-flex items-center justify-center -indent-px border-4 mr-2 border-background;
@apply md:ml-[-50px] md:mt-[-4px];
content: counter(step);
}
.chunk-container {
@apply shadow-none;
}
.chunk-container::after {
content: "";
@apply absolute -inset-4 shadow-xl rounded-xl border;
}
.border-grid {
@apply border-border/50 dark:border-border border-dashed;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.no-scrollbar {
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
}
.container-wrapper {
@apply max-w-[1400px] min-[1800px]:max-w-screen-2xl min-[1400px]:border-x border-border/70 dark:border-border mx-auto w-full border-dashed;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,27 @@
package navbar
templ Navbar(userName string) {
<nav class="py-1 border-b">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<div class="shrink-0 flex items-center">
<h1 class="text-xl font-bold text-primary">FlexSupport</h1>
</div>
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
<a href="/" class="border-transparent text-primary-foreground hover:border-gray-300 hover:text-primary-foreground/60 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
Dashboard
</a>
<a href="/tickets/new" class="border-transparent text-primary-foreground hover:border-gray-300 hover:text-primary-foreground/60 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
New Ticket
</a>
</div>
</div>
<div class="flex items-center">
<span class="text-sm text-gray-700">{ userName }</span>
@ThemeSwitcher()
</div>
</div>
</div>
</nav>
}

View File

@@ -1,7 +1,7 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.960
package modules
package navbar
//lint:file-ignore SA4006 This context is only used if a nested component is present.
@@ -29,20 +29,28 @@ func Navbar(userName string) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<nav class=\"py-1\"><div class=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\"><div class=\"flex justify-between h-16\"><div class=\"flex\"><div class=\"shrink-0 flex items-center\"><h1 class=\"text-xl font-bold text-gray-900\">FlexSupport</h1></div><div class=\"hidden sm:ml-6 sm:flex sm:space-x-8\"><a href=\"/\" class=\"border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\">Dashboard</a> <a href=\"/tickets\" class=\"border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\">Tickets</a> <a href=\"/tickets/new\" class=\"border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\">New Ticket</a> <a href=\"/technician\" class=\"border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\">Technician View</a></div></div><div class=\"flex items-center\"><span class=\"text-sm text-gray-700\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<nav class=\"py-1 border-b\"><div class=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\"><div class=\"flex justify-between h-16\"><div class=\"flex\"><div class=\"shrink-0 flex items-center\"><h1 class=\"text-xl font-bold text-primary\">FlexSupport</h1></div><div class=\"hidden sm:ml-6 sm:flex sm:space-x-8\"><a href=\"/\" class=\"border-transparent text-primary-foreground hover:border-gray-300 hover:text-primary-foreground/60 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\">Dashboard</a> <a href=\"/tickets/new\" class=\"border-transparent text-primary-foreground hover:border-gray-300 hover:text-primary-foreground/60 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\">New Ticket</a></div></div><div class=\"flex items-center\"><span class=\"text-sm text-gray-700\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(userName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/modules/navbar.templ`, Line: 27, Col: 51}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/components/navbar/navbar.templ`, Line: 21, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</span></div></div></div></nav>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = ThemeSwitcher().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div></div></div></nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -0,0 +1,92 @@
// from https://github.com/templui/templui
package navbar
import (
"flexsupport/ui/components/button"
"flexsupport/ui/components/icon"
)
type ThemeSwitcherProps struct {
Class string
}
templ ThemeSwitcher(props ...ThemeSwitcherProps) {
{{ var p ThemeSwitcherProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<script nonce={ templ.GetNonce(ctx) }>
(function() {
// Make theme functions globally available for the switcher
window.themeUtils = window.themeUtils || {};
// Get current theme preference (system, light, or dark)
window.themeUtils.getThemePreference = function() {
return localStorage.getItem('themePreference') || 'system';
}
// Apply theme based on preference
window.themeUtils.applyTheme = function() {
const preference = window.themeUtils.getThemePreference();
let isDark = false;
if (preference === 'system') {
// Use system preference
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
} else {
// Use explicit preference
isDark = preference === 'dark';
}
document.documentElement.classList.toggle('dark', isDark);
// Dispatch event for compatibility
document.dispatchEvent(new CustomEvent('theme-changed'));
}
// Toggle between light and dark (system only on initial state)
window.themeUtils.cycleTheme = function() {
const current = window.themeUtils.getThemePreference();
let next;
if (current === 'system') {
// First click from system state - determine based on current appearance
const isDarkNow = window.matchMedia('(prefers-color-scheme: dark)').matches;
next = isDarkNow ? 'light' : 'dark';
} else {
// Toggle between light and dark
next = current === 'light' ? 'dark' : 'light';
}
localStorage.setItem('themePreference', next);
window.themeUtils.applyTheme();
}
// Listen for system theme changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
if (window.themeUtils.getThemePreference() === 'system') {
window.themeUtils.applyTheme();
}
});
// Use event delegation for click handling
document.addEventListener('click', (e) => {
const themeSwitcher = e.target.closest('[data-theme-switcher]');
if (themeSwitcher) {
e.preventDefault();
window.themeUtils.cycleTheme();
}
});
})();
</script>
@button.Button(button.Props{
Size: button.SizeIcon,
Variant: button.VariantGhost,
Class: p.Class,
Attributes: templ.Attributes{
"data-theme-switcher": "true",
},
}) {
@icon.Eclipse(icon.Props{Size: 20})
}
}

View File

@@ -0,0 +1,97 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.960
// from https://github.com/templui/templui
package navbar
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"flexsupport/ui/components/button"
"flexsupport/ui/components/icon"
)
type ThemeSwitcherProps struct {
Class string
}
func ThemeSwitcher(props ...ThemeSwitcherProps) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var p ThemeSwitcherProps
if len(props) > 0 {
p = props[0]
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<script nonce=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(templ.GetNonce(ctx))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/components/navbar/themeswitcher.templ`, Line: 18, Col: 36}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\">\n\t\t(function() {\n\t\t\t// Make theme functions globally available for the switcher\n\t\t\twindow.themeUtils = window.themeUtils || {};\n\t\t\t\n\t\t\t// Get current theme preference (system, light, or dark)\n\t\t\twindow.themeUtils.getThemePreference = function() {\n\t\t\t\treturn localStorage.getItem('themePreference') || 'system';\n\t\t\t}\n\n\t\t\t// Apply theme based on preference\n\t\t\twindow.themeUtils.applyTheme = function() {\n\t\t\t\tconst preference = window.themeUtils.getThemePreference();\n\t\t\t\tlet isDark = false;\n\n\t\t\t\tif (preference === 'system') {\n\t\t\t\t\t// Use system preference\n\t\t\t\t\tisDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n\t\t\t\t} else {\n\t\t\t\t\t// Use explicit preference\n\t\t\t\t\tisDark = preference === 'dark';\n\t\t\t\t}\n\n\t\t\t\tdocument.documentElement.classList.toggle('dark', isDark);\n\t\t\t\t\n\t\t\t\t// Dispatch event for compatibility\n\t\t\t\tdocument.dispatchEvent(new CustomEvent('theme-changed'));\n\t\t\t}\n\n\t\t\t// Toggle between light and dark (system only on initial state)\n\t\t\twindow.themeUtils.cycleTheme = function() {\n\t\t\t\tconst current = window.themeUtils.getThemePreference();\n\t\t\t\tlet next;\n\t\t\t\t\n\t\t\t\tif (current === 'system') {\n\t\t\t\t\t// First click from system state - determine based on current appearance\n\t\t\t\t\tconst isDarkNow = window.matchMedia('(prefers-color-scheme: dark)').matches;\n\t\t\t\t\tnext = isDarkNow ? 'light' : 'dark';\n\t\t\t\t} else {\n\t\t\t\t\t// Toggle between light and dark\n\t\t\t\t\tnext = current === 'light' ? 'dark' : 'light';\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tlocalStorage.setItem('themePreference', next);\n\t\t\t\twindow.themeUtils.applyTheme();\n\t\t\t}\n\n\t\t\t// Listen for system theme changes\n\t\t\twindow.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {\n\t\t\t\tif (window.themeUtils.getThemePreference() === 'system') {\n\t\t\t\t\twindow.themeUtils.applyTheme();\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Use event delegation for click handling\n\t\t\tdocument.addEventListener('click', (e) => {\n\t\t\t\tconst themeSwitcher = e.target.closest('[data-theme-switcher]');\n\t\t\t\tif (themeSwitcher) {\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\twindow.themeUtils.cycleTheme();\n\t\t\t\t}\n\t\t\t});\n\t\t})();\n\t</script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var3 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = icon.Eclipse(icon.Props{Size: 20}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = button.Button(button.Props{
Size: button.SizeIcon,
Variant: button.VariantGhost,
Class: p.Class,
Attributes: templ.Attributes{
"data-theme-switcher": "true",
},
}).Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -1,32 +0,0 @@
package modules
templ Navbar(userName string) {
<nav class="py-1">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<div class="shrink-0 flex items-center">
<h1 class="text-xl font-bold text-gray-900">FlexSupport</h1>
</div>
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
<a href="/" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
Dashboard
</a>
<a href="/tickets" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
Tickets
</a>
<a href="/tickets/new" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
New Ticket
</a>
<a href="/technician" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
Technician View
</a>
</div>
</div>
<div class="flex items-center">
<span class="text-sm text-gray-700">{ userName }</span>
</div>
</div>
</div>
</nav>
}

View File

@@ -0,0 +1,48 @@
package rows
import (
"flexsupport/internal/models"
"flexsupport/internal/utils"
"fmt"
"flexsupport/ui/components/table"
)
templ TicketRows(tickets []models.Ticket) {
for _, ticket := range tickets {
{{ ticketUrl := fmt.Sprintf("/tickets/%d", ticket.ID) }}
@table.Row() {
@table.Cell() {
<a href={ ticketUrl } class="text-blue-600 hover:text-blue-900">{ ticket.ID }</a>
}
@table.Cell() {
<div class="text-sm font-medium text-primary-foreground">{ ticket.CustomerName }</div>
<div class="text-sm ">{ ticket.CustomerEmail }</div>
}
@table.Cell() {
{ ticket.ItemType }
}
@table.Cell() {
<span
class={ utils.TwMerge("px-2 inline-flex text-xs leading-5 font-semibold rounded-full", ticket.StatusClass()) }
>
{ ticket.StatusDisplay() }
</span>
}
@table.Cell() {
if ticket.AssignedTo != "" {
{ ticket.AssignedTo }
} else {
<span class="text-gray-400">Unassigned</span>
}
}
@table.Cell() {
{ ticket.DueDate.Format("2006-01-02") }
}
@table.Cell() {
<a href={ fmt.Sprintf("/tickets/%d/edit", ticket.ID) } class="text-blue-600 hover:text-blue-900">Edit</a>
<a href={ ticketUrl } class="text-gray-600 hover:text-gray-900">View</a>
}
}
}
}

View File

@@ -0,0 +1,374 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.960
package rows
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"flexsupport/internal/models"
"flexsupport/internal/utils"
"fmt"
"flexsupport/ui/components/table"
)
func TicketRows(tickets []models.Ticket) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
for _, ticket := range tickets {
ticketUrl := fmt.Sprintf("/tickets/%d", ticket.ID)
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(ticketUrl)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/rows/ticketRows.templ`, Line: 16, Col: 23}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" class=\"text-blue-600 hover:text-blue-900\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(ticket.ID)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/rows/ticketRows.templ`, Line: 16, Col: 79}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Cell().Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var6 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"text-sm font-medium text-primary-foreground\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(ticket.CustomerName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/rows/ticketRows.templ`, Line: 19, Col: 82}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div><div class=\"text-sm \">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(ticket.CustomerEmail)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/rows/ticketRows.templ`, Line: 20, Col: 48}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Cell().Render(templ.WithChildren(ctx, templ_7745c5c3_Var6), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var9 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(ticket.ItemType)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/rows/ticketRows.templ`, Line: 23, Col: 21}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Cell().Render(templ.WithChildren(ctx, templ_7745c5c3_Var9), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var11 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
var templ_7745c5c3_Var12 = []any{utils.TwMerge("px-2 inline-flex text-xs leading-5 font-semibold rounded-full", ticket.StatusClass())}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<span class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var12).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/rows/ticketRows.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(ticket.StatusDisplay())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/rows/ticketRows.templ`, Line: 29, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Cell().Render(templ.WithChildren(ctx, templ_7745c5c3_Var11), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var15 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
if ticket.AssignedTo != "" {
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(ticket.AssignedTo)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/rows/ticketRows.templ`, Line: 34, Col: 24}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<span class=\"text-gray-400\">Unassigned</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
templ_7745c5c3_Err = table.Cell().Render(templ.WithChildren(ctx, templ_7745c5c3_Var15), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var17 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(ticket.DueDate.Format("2006-01-02"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/rows/ticketRows.templ`, Line: 40, Col: 41}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Cell().Render(templ.WithChildren(ctx, templ_7745c5c3_Var17), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var19 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 templ.SafeURL
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinURLErrs(fmt.Sprintf("/tickets/%d/edit", ticket.ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/rows/ticketRows.templ`, Line: 43, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\" class=\"text-blue-600 hover:text-blue-900\">Edit</a> <a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 templ.SafeURL
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinURLErrs(ticketUrl)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/rows/ticketRows.templ`, Line: 44, Col: 23}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" class=\"text-gray-600 hover:text-gray-900\">View</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Cell().Render(templ.WithChildren(ctx, templ_7745c5c3_Var19), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Row().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -0,0 +1,45 @@
package search
import "flexsupport/ui/components/input"
templ SearchTickets(term, status string) {
<span class="htmx-indicator">
<img src="/assets/icons/loader.svg" class="size-4 animate-spin" alt="Loading..."/> Loading...
</span>
<div class="flex-1">
@input.Input(input.Props{
Type: input.TypeText,
ID: "search",
Name: "search",
Value: term,
Placeholder: "Search tickets...",
Attributes: templ.Attributes{
"hx-get": "/tickets",
"hx-trigger": "keyup changed delay:300ms",
"hx-target": "#ticket-list",
"hx-indicator": ".htmx-indicator",
"hx-replace": "innerHTML",
},
})
</div>
<div class="flex gap-2">
<select
name="status"
id="status"
class="block w-full pl-3 pr-10 py-2 text-base border-gray-600 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md"
hx-get="/tickets"
value={ status }
hx-trigger="change"
hx-target="#ticket-list"
hx-swap="innerHTML"
hx-include="#search"
>
<option value="">All Statuses</option>
<option value="new" selected={ status == "new" }>New</option>
<option value="in_progress" selected={ status == "in_progress" }>In Progress</option>
<option value="waiting_parts" selected={ status == "waiting_parts" }>Waiting for Parts</option>
<option value="ready" selected={ status == "ready" }>Ready for Pickup</option>
<option value="completed" selected={ status == "completed" }>Completed</option>
</select>
</div>
}

View File

@@ -0,0 +1,141 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.960
package search
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import "flexsupport/ui/components/input"
func SearchTickets(term, status string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<span class=\"htmx-indicator\"><img src=\"/assets/icons/loader.svg\" class=\"size-4 animate-spin\" alt=\"Loading...\"> Loading...</span><div class=\"flex-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = input.Input(input.Props{
Type: input.TypeText,
ID: "search",
Name: "search",
Value: term,
Placeholder: "Search tickets...",
Attributes: templ.Attributes{
"hx-get": "/tickets",
"hx-trigger": "keyup changed delay:300ms",
"hx-target": "#ticket-list",
"hx-indicator": ".htmx-indicator",
"hx-replace": "innerHTML",
},
}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</div><div class=\"flex gap-2\"><select name=\"status\" id=\"status\" class=\"block w-full pl-3 pr-10 py-2 text-base border-gray-600 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md\" hx-get=\"/tickets\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(status)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/search/tickets.templ`, Line: 31, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" hx-trigger=\"change\" hx-target=\"#ticket-list\" hx-swap=\"innerHTML\" hx-include=\"#search\"><option value=\"\">All Statuses</option> <option value=\"new\" selected=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(status == "new")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/search/tickets.templ`, Line: 38, Col: 49}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\">New</option> <option value=\"in_progress\" selected=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(status == "in_progress")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/search/tickets.templ`, Line: 39, Col: 65}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\">In Progress</option> <option value=\"waiting_parts\" selected=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(status == "waiting_parts")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/search/tickets.templ`, Line: 40, Col: 69}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\">Waiting for Parts</option> <option value=\"ready\" selected=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(status == "ready")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/search/tickets.templ`, Line: 41, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\">Ready for Pickup</option> <option value=\"completed\" selected=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(status == "completed")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/search/tickets.templ`, Line: 42, Col: 61}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\">Completed</option></select></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -0,0 +1,56 @@
package tables
import (
"flexsupport/internal/models"
"flexsupport/ui/partials/rows"
"flexsupport/ui/components/table"
)
templ TicketsTable(tickets []models.Ticket) {
<div
class=" shadow rounded-lg overflow-hidden"
hx-get="/tickets"
hx-target="#ticket-list"
hx-trigger="load, every 30s"
hx-swap="innerHTML"
>
@table.Table(table.Props{
Class: "min-w-full ",
}) {
@table.Header() {
@table.Row() {
@table.Head() {
Ticket #
}
@table.Head() {
Customer
}
@table.Head() {
Item
}
@table.Head() {
Status
}
@table.Head() {
Assigned To
}
@table.Head() {
Due Date
}
@table.Head() {
Actions
}
}
}
@table.Body(table.BodyProps{
ID: "ticket-list",
}) {
@rows.TicketRows(tickets)
}
if len(tickets) == 0 {
<tr><td colspan="7">No Tickets</td></tr>
}
}
</div>
}

View File

@@ -0,0 +1,323 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.960
package tables
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"flexsupport/internal/models"
"flexsupport/ui/partials/rows"
"flexsupport/ui/components/table"
)
func TicketsTable(tickets []models.Ticket) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\" shadow rounded-lg overflow-hidden\" hx-get=\"/tickets\" hx-target=\"#ticket-list\" hx-trigger=\"load, every 30s\" hx-swap=\"innerHTML\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "Ticket #")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Head().Render(templ.WithChildren(ctx, templ_7745c5c3_Var5), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var6 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "Customer")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Head().Render(templ.WithChildren(ctx, templ_7745c5c3_Var6), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var7 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "Item")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Head().Render(templ.WithChildren(ctx, templ_7745c5c3_Var7), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var8 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "Status")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Head().Render(templ.WithChildren(ctx, templ_7745c5c3_Var8), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var9 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "Assigned To")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Head().Render(templ.WithChildren(ctx, templ_7745c5c3_Var9), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var10 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "Due Date")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Head().Render(templ.WithChildren(ctx, templ_7745c5c3_Var10), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var11 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "Actions")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Head().Render(templ.WithChildren(ctx, templ_7745c5c3_Var11), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Row().Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Header().Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var12 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = rows.TicketRows(tickets).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = table.Body(table.BodyProps{
ID: "ticket-list",
}).Render(templ.WithChildren(ctx, templ_7745c5c3_Var12), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(tickets) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<tr><td colspan=\"7\">No Tickets</td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
templ_7745c5c3_Err = table.Table(table.Props{
Class: "min-w-full ",
}).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate