chore: database
This commit is contained in:
@@ -5,7 +5,7 @@ tmp_dir = "tmp"
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "./tmp/main"
|
||||
cmd = "go build -o ./tmp/main ."
|
||||
cmd = "task gen && go build -o ./tmp/main ."
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||
exclude_file = []
|
||||
|
||||
4
app.go
4
app.go
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
|
||||
cfg "flexsupport/internal/config"
|
||||
db "flexsupport/internal/domain"
|
||||
"flexsupport/internal/lib/logger"
|
||||
"flexsupport/internal/router"
|
||||
)
|
||||
@@ -32,7 +33,8 @@ func App(ctx context.Context, stdout io.Writer, getenv func(string, string) stri
|
||||
default:
|
||||
log = slog.New(slog.NewJSONHandler(stdout, logOptions))
|
||||
}
|
||||
r := router.NewRouter(log)
|
||||
r := router.NewRouter(log, config)
|
||||
db.NewDB(ctx, config.DatabaseUrl)
|
||||
|
||||
fmt.Println("Starting server on :8080")
|
||||
return http.ListenAndServe(":8080", r)
|
||||
|
||||
@@ -16,6 +16,7 @@ type Config struct {
|
||||
VerboseLogging bool `mapstructure:"VERBOSE_LOGGING"`
|
||||
Environment Env `mapstructure:"ENVIRONMENT"`
|
||||
Domain string `mapstructure:"DOMAIN"`
|
||||
DatabaseUrl string `mapstructure:"DATABASE_URL"`
|
||||
}
|
||||
|
||||
func New(getenv func(string, string) string) *Config {
|
||||
@@ -24,6 +25,7 @@ func New(getenv func(string, string) string) *Config {
|
||||
VerboseLogging: getenv("VERBOSE_LOGGING", "false") == "true",
|
||||
Environment: Env(getenv("ENVIRONMENT", "development")),
|
||||
Domain: getenv("DOMAIN", "http://localhost:8080"),
|
||||
DatabaseUrl: getenv("DATABASE_URL", "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable"),
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
159
internal/domain/migrations.go
Normal file
159
internal/domain/migrations.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrationFiles embed.FS
|
||||
|
||||
type Migration struct {
|
||||
Version int64
|
||||
Name string
|
||||
SQL string
|
||||
}
|
||||
|
||||
func (db *DB) getAppliedMigrations(ctx context.Context) (map[int64]bool, error) {
|
||||
rows, err := db.QueryContext(ctx, "SELECT version FROM schema_migrations")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
err = rows.Close()
|
||||
if err != nil {
|
||||
slog.Error("Failed to close rows", "error", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
applied := make(map[int64]bool)
|
||||
for rows.Next() {
|
||||
var version int64
|
||||
if err := rows.Scan(&version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
applied[version] = true
|
||||
}
|
||||
slog.Info("Applied migrations", "count", len(applied))
|
||||
return applied, rows.Err()
|
||||
}
|
||||
|
||||
func (db *DB) runMigrations(ctx context.Context) error {
|
||||
applied, err := db.getAppliedMigrations(ctx)
|
||||
if err != nil {
|
||||
return NewIgnorableError("failed to get applied migrations: " + err.Error())
|
||||
}
|
||||
|
||||
migrations, err := loadMigrations()
|
||||
if err != nil {
|
||||
return NewIgnorableError("failed to load migrations: " + err.Error())
|
||||
}
|
||||
|
||||
tx, err := db.Beginx()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
for _, migration := range migrations {
|
||||
if applied[migration.Version] {
|
||||
continue
|
||||
}
|
||||
slog.Info("Applying migration",
|
||||
slog.Int64("version", migration.Version),
|
||||
slog.String("name", migration.Name))
|
||||
if _, err := tx.ExecContext(ctx, migration.SQL); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return fmt.Errorf("failed to apply migration %d: %w", migration.Version, err)
|
||||
}
|
||||
|
||||
insertSQL := "INSERT INTO schema_migrations (version, name) VALUES ($1, $2)"
|
||||
|
||||
_, err = tx.ExecContext(ctx, insertSQL, migration.Version, migration.Name)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return fmt.Errorf("migration %d failed: %w", migration.Version, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadMigrations() ([]Migration, error) {
|
||||
entries, err := migrationFiles.ReadDir("migrations")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slog.Info("Loading migrations")
|
||||
slog.Info("Found migrations", "count", len(entries))
|
||||
migrations := make([]Migration, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
slog.Info(entry.Name())
|
||||
if !strings.HasSuffix(entry.Name(), ".sql") {
|
||||
slog.Info("Skipping migration", "name", entry.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.SplitN(entry.Name(), "_", 2)
|
||||
if len(parts) < 2 {
|
||||
slog.Info("Skipping migration", "name", entry.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
version, err := strconv.ParseInt(parts[0], 10, 64)
|
||||
if err != nil {
|
||||
slog.Info("Skipping migration", "name", entry.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
content, err := migrationFiles.ReadFile(filepath.Join("migrations", entry.Name()))
|
||||
if err != nil {
|
||||
slog.Error("Failed to read migration file", "name", entry.Name(), "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := strings.TrimSuffix(parts[1], ".sql")
|
||||
|
||||
migrations = append(migrations, Migration{
|
||||
Version: version,
|
||||
Name: name,
|
||||
SQL: string(content),
|
||||
})
|
||||
}
|
||||
|
||||
if len(migrations) == 0 {
|
||||
return migrations, nil
|
||||
}
|
||||
|
||||
sort.Slice(migrations, func(i, j int) bool {
|
||||
return migrations[i].Version < migrations[j].Version
|
||||
})
|
||||
migrations = slices.Clip(migrations)
|
||||
|
||||
return migrations, nil
|
||||
}
|
||||
|
||||
type IgnorableError struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (ie IgnorableError) Error() string {
|
||||
return ie.msg
|
||||
}
|
||||
|
||||
func NewIgnorableError(message string) error {
|
||||
return &IgnorableError{msg: message}
|
||||
}
|
||||
|
||||
var ErrIgnorable = &IgnorableError{}
|
||||
@@ -1,3 +1,281 @@
|
||||
CREATE TABLE IF NOT EXISTS users(
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
)
|
||||
create extension if not exists citext;
|
||||
create extension if not exists pgcrypto;
|
||||
|
||||
create table if not exists users (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
name text not null,
|
||||
email citext unique not null,
|
||||
email_verified boolean not null default false,
|
||||
password_hash text,
|
||||
is_system_admin boolean not null default false,
|
||||
created_at timestamptz not null default now(),
|
||||
last_login_at timestamptz
|
||||
);
|
||||
|
||||
create table if not exists tenants (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
slug text not null unique,
|
||||
name text not null,
|
||||
logo_url text,
|
||||
timezone text not null default 'UTC',
|
||||
created_at timestamptz not null default now(),
|
||||
onboarding_completed_at timestamptz
|
||||
);
|
||||
|
||||
create table if not exists tenant_memberships (
|
||||
tenant_id uuid not null references tenants (id) on delete cascade,
|
||||
user_id uuid not null references users(id) on delete cascade,
|
||||
status text not null default 'active', -- active/invited/disabled
|
||||
created_at timestamptz not null default now(),
|
||||
primary key (tenant_id, user_id)
|
||||
);
|
||||
|
||||
create index on tenant_memberships (user_id);
|
||||
|
||||
create table if not exists roles (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references tenants (id) on delete cascade,
|
||||
name text not null,
|
||||
is_system boolean not null default false,
|
||||
unique (tenant_id, name)
|
||||
);
|
||||
|
||||
create table if not exists permissions (
|
||||
key text primary key, -- e.g. 'ticket.read', 'ticket.write', 'project.admin', etc.
|
||||
description text
|
||||
);
|
||||
|
||||
create table if not exists role_permissions (
|
||||
role_id uuid not null references roles(id) on delete cascade,
|
||||
permission_key text not null references permissions(key) on delete cascade,
|
||||
primary key (role_id, permission_key)
|
||||
);
|
||||
|
||||
create table if not exists membership_roles (
|
||||
tenant_id uuid not null,
|
||||
user_id uuid not null,
|
||||
role_id uuid not null references roles(id) on delete cascade,
|
||||
primary key (tenant_id, user_id, role_id),
|
||||
foreign key (tenant_id, user_id) references tenant_memberships (tenant_id, user_id) on delete cascade
|
||||
);
|
||||
|
||||
create table if not exists projects (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references tenants (id) on delete cascade,
|
||||
key text not null, -- IT, HR, etc
|
||||
name text not null,
|
||||
description text,
|
||||
is_archived boolean not null default false,
|
||||
created_at timestamptz not null default now(),
|
||||
unique (tenant_id, key),
|
||||
unique (tenant_id, name)
|
||||
);
|
||||
|
||||
create index on projects (tenant_id);
|
||||
|
||||
create table if not exists project_portal_settings (
|
||||
project_id uuid primary key references projects(id) on delete cascade,
|
||||
enabled boolean not null default false
|
||||
);
|
||||
|
||||
create table if not exists project_memberships (
|
||||
tenant_id uuid not null references tenants(id) on delete cascade,
|
||||
project_id uuid not null references projects(id) on delete cascade,
|
||||
user_id uuid not null references users(id) on delete cascade,
|
||||
created_at timestamptz not null default now(),
|
||||
primary key (tenant_id, project_id, user_id)
|
||||
);
|
||||
|
||||
create index on project_memberships (tenant_id, user_id);
|
||||
|
||||
|
||||
create table if not exists request_types (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references tenants(id) on delete cascade,
|
||||
project_id uuid not null references projects(id) on delete cascade,
|
||||
|
||||
key text not null, -- "maintenance_request"
|
||||
name text not null, -- "Maintenance Request"
|
||||
description text,
|
||||
is_archived boolean not null default false,
|
||||
|
||||
sort_order int not null default 0,
|
||||
|
||||
unique (tenant_id, project_id, key),
|
||||
unique (tenant_id, project_id, id)
|
||||
);
|
||||
|
||||
create index on request_types (tenant_id, project_id);
|
||||
create table if not exists request_type_fields (
|
||||
tenant_id uuid not null references tenants(id) on delete cascade,
|
||||
request_type_id uuid not null references request_types(id) on delete cascade,
|
||||
field_id uuid not null references custom_fields(id) on delete restrict,
|
||||
|
||||
sort_order int not null default 0,
|
||||
|
||||
-- per ticket type behavior
|
||||
required boolean not null default false,
|
||||
requester_visible boolean not null default true,
|
||||
|
||||
primary key (tenant_id, request_type_id, field_id)
|
||||
);
|
||||
|
||||
create index on request_type_fields (tenant_id, request_type_id);
|
||||
|
||||
create table if not exists tickets (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references tenants(id) on delete cascade,
|
||||
project_id uuid not null references projects(id) on delete restrict,
|
||||
request_type_id uuid not null,
|
||||
|
||||
ticket_number bigint not null,
|
||||
title text not null,
|
||||
description text,
|
||||
|
||||
status text not null,
|
||||
priority text,
|
||||
|
||||
created_by_user_id uuid references users(id),
|
||||
requester_user_id uuid references users(id),
|
||||
assigned_to_user_id uuid references users(id),
|
||||
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
closed_at timestamptz,
|
||||
|
||||
unique (project_id, ticket_number),
|
||||
constraint tickets_request_type_same_project_fk
|
||||
foreign key (tenant_id, project_id, request_type_id)
|
||||
references request_types (tenant_id, project_id, id)
|
||||
on delete restrict
|
||||
);
|
||||
|
||||
create index if not exists tickets_tenant_project_status_idx
|
||||
on tickets (tenant_id, project_id, status);
|
||||
|
||||
create index if not exists tickets_tenant_requester_idx
|
||||
on tickets (tenant_id, requester_user_id);
|
||||
|
||||
create index if not exists tickets_tenant_assigned_idx
|
||||
on tickets (tenant_id, assigned_to_user_id);
|
||||
|
||||
create table if not exists custom_fields (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references tenants(id) on delete cascade,
|
||||
|
||||
key text not null, -- stable programmatic key: "asset_tag"
|
||||
name text not null, -- display name: "Asset Tag"
|
||||
description text,
|
||||
|
||||
field_type text not null, -- 'text','textarea','number','bool','date','datetime',
|
||||
-- 'select','multiselect','user','group' (future)
|
||||
|
||||
is_archived boolean not null default false,
|
||||
|
||||
created_at timestamptz not null default now(),
|
||||
|
||||
unique (tenant_id, key)
|
||||
);
|
||||
|
||||
|
||||
create index if not exists custom_fields_tenant_idx
|
||||
on custom_fields (tenant_id);
|
||||
|
||||
|
||||
create table if not exists custom_field_options (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references tenants(id) on delete cascade,
|
||||
field_id uuid not null references custom_fields(id) on delete cascade,
|
||||
|
||||
value text not null, -- stable value stored in ticket
|
||||
label text not null, -- display label
|
||||
sort_order int not null default 0,
|
||||
is_archived boolean not null default false,
|
||||
|
||||
unique (field_id, value)
|
||||
);
|
||||
|
||||
create index on custom_field_options (tenant_id, field_id);
|
||||
|
||||
create table if not exists ticket_field_values (
|
||||
tenant_id uuid not null references tenants(id) on delete cascade,
|
||||
ticket_id uuid not null references tickets(id) on delete cascade,
|
||||
field_id uuid not null references custom_fields(id) on delete cascade,
|
||||
|
||||
value jsonb not null, -- e.g. {"text":"abc"} or {"option":"laptop"} or {"user_id":"..."}
|
||||
updated_at timestamptz not null default now(),
|
||||
|
||||
primary key (tenant_id, ticket_id, field_id)
|
||||
);
|
||||
|
||||
create index on ticket_field_values (tenant_id, field_id);
|
||||
create index ticket_field_values_value_gin
|
||||
on ticket_field_values using gin (value);
|
||||
|
||||
create table if not exists ticket_comments (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references tenants(id) on delete cascade,
|
||||
ticket_id uuid not null references tickets(id) on delete cascade,
|
||||
author_user_id uuid references users(id),
|
||||
body text not null,
|
||||
is_internal boolean not null default false,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create index on ticket_comments (tenant_id, ticket_id, created_at);
|
||||
|
||||
create table if not exists ticket_events (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references tenants(id) on delete cascade,
|
||||
ticket_id uuid not null references tickets(id) on delete cascade,
|
||||
actor_user_id uuid references users(id),
|
||||
type text not null, -- 'status_changed', 'comment_added', etc
|
||||
payload jsonb not null default '{}'::jsonb,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create index on ticket_events (tenant_id, ticket_id, created_at);
|
||||
|
||||
|
||||
create table if not exists tenant_settings (
|
||||
tenant_id uuid primary key references tenants(id) on delete cascade,
|
||||
|
||||
public_portal_enabled boolean not null default false,
|
||||
|
||||
portal_title text,
|
||||
portal_welcome_text text,
|
||||
|
||||
outbound_from_name text,
|
||||
outbound_from_email citext
|
||||
);
|
||||
|
||||
create table if not exists integrations (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references tenants(id) on delete cascade,
|
||||
integration_type text not null, -- 'shopify', 'oidc', 'webhook', 'smtp', etc
|
||||
name text not null,
|
||||
enabled boolean not null default false,
|
||||
config jsonb not null default '{}'::jsonb,
|
||||
created_at timestamptz not null default now(),
|
||||
unique (tenant_id, integration_type, name)
|
||||
);
|
||||
|
||||
create index on integrations (tenant_id, integration_type);
|
||||
|
||||
|
||||
create table if not exists tenant_domains (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null references tenants(id) on delete cascade,
|
||||
hostname text not null unique, -- "acme.flexsupport.com" or "support.acme.com"
|
||||
is_primary boolean not null default false,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create index if not exists tenant_domains_tenant_idx
|
||||
on tenant_domains (tenant_id);
|
||||
|
||||
create table if not exists schema_migrations (
|
||||
version integer primary key,
|
||||
name text not null,
|
||||
applied_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
// "flexsupport/ui/components/dropdown"
|
||||
"flexsupport/ui/components/input"
|
||||
"flexsupport/ui/components/label"
|
||||
// "flexsupport/ui/components/popover"
|
||||
// "flexsupport/ui/components/selectbox"
|
||||
"flexsupport/ui/components/popover"
|
||||
"flexsupport/ui/components/selectbox"
|
||||
// "flexsupport/ui/components/textarea"
|
||||
"flexsupport/ui/components/dialog"
|
||||
// "flexsupport/ui/components/toast"
|
||||
@@ -31,6 +31,8 @@ templ BaseLayout(contents templ.Component) {
|
||||
@input.Script()
|
||||
@label.Script()
|
||||
@dialog.Script()
|
||||
@selectbox.Script()
|
||||
@popover.Script()
|
||||
<script nonce={ templ.GetNonce(ctx) }>
|
||||
(function() {
|
||||
// Get current theme preference (system, light, or dark)
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
// "flexsupport/ui/components/dropdown"
|
||||
"flexsupport/ui/components/input"
|
||||
"flexsupport/ui/components/label"
|
||||
// "flexsupport/ui/components/popover"
|
||||
// "flexsupport/ui/components/selectbox"
|
||||
"flexsupport/ui/components/popover"
|
||||
"flexsupport/ui/components/selectbox"
|
||||
// "flexsupport/ui/components/textarea"
|
||||
"flexsupport/ui/components/dialog"
|
||||
// "flexsupport/ui/components/toast"
|
||||
@@ -63,6 +63,14 @@ func BaseLayout(contents templ.Component) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = selectbox.Script().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = popover.Script().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<script nonce=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
@@ -70,7 +78,7 @@ func BaseLayout(contents templ.Component) templ.Component {
|
||||
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: 34, Col: 38}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/layout/base.templ`, Line: 36, Col: 38}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
||||
56
internal/middleware/tenant.go
Normal file
56
internal/middleware/tenant.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ctxKey string
|
||||
|
||||
const (
|
||||
ctxTenantID ctxKey = "tenant_id"
|
||||
ctxTenantSlug ctxKey = "tenant_slug"
|
||||
)
|
||||
|
||||
type Tenant struct {
|
||||
ID string
|
||||
Slug string
|
||||
Name string
|
||||
}
|
||||
|
||||
type TenantResolver interface {
|
||||
ResolveByHost(ctx context.Context, host string) (*Tenant, bool, error)
|
||||
ResolveBySlug(ctx context.Context, slug string) (*Tenant, bool, error)
|
||||
}
|
||||
|
||||
func TenantMiddleware(resolver TenantResolver, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
host := r.Host
|
||||
tenant, ok, err := resolver.ResolveByHost(r.Context(), host)
|
||||
if err != nil {
|
||||
http.Error(w, "tenant lookup failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// // Optional: fallback to /t/{slug}/... (dev mode)
|
||||
// if !ok {
|
||||
// slug, ok2 := tenantSlugFromPath(r.URL.Path) // you implement
|
||||
// if ok2 {
|
||||
// tenant, ok, err = resolver.ResolveBySlug(r.Context(), slug)
|
||||
// if err != nil {
|
||||
// http.Error(w, "tenant lookup failed", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), ctxTenantID, tenant.ID)
|
||||
ctx = context.WithValue(ctx, ctxTenantSlug, tenant.Slug)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"flexsupport/internal/config"
|
||||
mw "flexsupport/internal/middleware"
|
||||
"flexsupport/static"
|
||||
|
||||
@@ -16,18 +17,29 @@ import (
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
)
|
||||
|
||||
var AppDev string = "development"
|
||||
|
||||
func NewRouter(log *slog.Logger) *chi.Mux {
|
||||
func NewRouter(log *slog.Logger, cfg *config.Config) *chi.Mux {
|
||||
r := chi.NewMux()
|
||||
// Dashboard
|
||||
|
||||
r.Handle("/assets/*",
|
||||
disableCacheInDevMode(
|
||||
http.StripPrefix("/assets/",
|
||||
static.AssetRouter()),
|
||||
),
|
||||
)
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(
|
||||
middleware.Compress(5),
|
||||
)
|
||||
r.Handle("/assets/*",
|
||||
disableCacheInDevMode(
|
||||
http.StripPrefix("/assets/",
|
||||
static.AssetRouter(cfg)),
|
||||
cfg,
|
||||
),
|
||||
)
|
||||
r.Handle("/public/*",
|
||||
disableCacheInDevMode(
|
||||
http.StripPrefix("/public/",
|
||||
static.PublicRouter(cfg)),
|
||||
cfg,
|
||||
),
|
||||
)
|
||||
})
|
||||
// Tickets
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(
|
||||
@@ -45,8 +57,8 @@ func NewRouter(log *slog.Logger) *chi.Mux {
|
||||
return r
|
||||
}
|
||||
|
||||
func disableCacheInDevMode(next http.Handler) http.Handler {
|
||||
if AppDev == "development" {
|
||||
func disableCacheInDevMode(next http.Handler, cfg *config.Config) http.Handler {
|
||||
if cfg.Environment == config.PROD {
|
||||
return next
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -3,6 +3,8 @@ package dashboard
|
||||
import (
|
||||
"flexsupport/internal/models"
|
||||
"flexsupport/ui/components/card"
|
||||
|
||||
"flexsupport/ui/components/button"
|
||||
"flexsupport/ui/partials/tables"
|
||||
"flexsupport/ui/partials/search"
|
||||
)
|
||||
@@ -138,19 +140,15 @@ templ Dashboard(tickets []models.Ticket, isMobile bool) {
|
||||
</div>
|
||||
if !isMobile {
|
||||
<!-- 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("", "")
|
||||
<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 class="flex flex-row items-center my-2 justify-between gap-4">
|
||||
@search.SearchTickets("", "")
|
||||
@button.Button(button.Props{
|
||||
Href: "/tickets/new",
|
||||
Variant: button.VariantOutline,
|
||||
}) {
|
||||
New Ticket
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@tables.TicketsTable(tickets, isMobile)
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,8 @@ import templruntime "github.com/a-h/templ/runtime"
|
||||
import (
|
||||
"flexsupport/internal/models"
|
||||
"flexsupport/ui/components/card"
|
||||
|
||||
"flexsupport/ui/components/button"
|
||||
"flexsupport/ui/partials/search"
|
||||
"flexsupport/ui/partials/tables"
|
||||
)
|
||||
@@ -205,7 +207,11 @@ func Dashboard(tickets []models.Ticket, isMobile bool) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !isMobile {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<!-- Filters and Search --> ")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<!-- Filters and Search --> <div class=\"flex flex-row items-center my-2 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
|
||||
}
|
||||
@@ -221,39 +227,20 @@ func Dashboard(tickets []models.Ticket, isMobile bool) templ.Component {
|
||||
}()
|
||||
}
|
||||
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, 8, "<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, 9, "<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)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "New Ticket")
|
||||
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)
|
||||
templ_7745c5c3_Err = button.Button(button.Props{
|
||||
Href: "/tickets/new",
|
||||
Variant: button.VariantOutline,
|
||||
}).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, 9, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
13
main.go
13
main.go
@@ -4,7 +4,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
// "os/exec"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
@@ -14,9 +15,13 @@ var Environment = "development"
|
||||
func init() {
|
||||
os.Setenv("env", Environment)
|
||||
if Environment == "development" {
|
||||
// exec.Command("bunx", "tailwindcss", "-i", "./static/assets/css/input.css", "-o", "./static/assets/css/output.min.css", "-m").Run()
|
||||
// exec.Command("go", "tool", "templ", "generate")
|
||||
exec.Command("task", "gen").Run()
|
||||
// // exec.Command("bunx", "tailwindcss", "-i", "./static/assets/css/input.css", "-o", "./static/assets/css/output.min.css", "-m").Run()
|
||||
// // exec.Command("go", "tool", "templ", "generate")
|
||||
// out, err := exec.Command("task", "gen").Output()
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// fmt.Printf("%s\n", out)
|
||||
err := godotenv.Load(".env")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -2,9 +2,12 @@ package static
|
||||
|
||||
import (
|
||||
"embed"
|
||||
// "fmt"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
|
||||
"flexsupport/internal/config"
|
||||
// "fmt"
|
||||
// "strings"
|
||||
// "github.com/go-chi/chi/v5"
|
||||
// "github.com/go-chi/chi/v5/middleware"
|
||||
@@ -14,10 +17,13 @@ import (
|
||||
//go:embed all:assets
|
||||
var Static embed.FS
|
||||
|
||||
var AppDev string = "development"
|
||||
//go:embed all:public
|
||||
var Public embed.FS
|
||||
|
||||
func AssetRouter() http.Handler {
|
||||
if AppDev == "development" {
|
||||
func AssetRouter(cfg *config.Config) http.Handler {
|
||||
if cfg.Environment == config.DEV {
|
||||
|
||||
fmt.Println("ASSETS DEV")
|
||||
return http.FileServer(http.Dir("./static/assets"))
|
||||
}
|
||||
st, err := fs.Sub(Static, "assets")
|
||||
@@ -28,6 +34,19 @@ func AssetRouter() http.Handler {
|
||||
return handler
|
||||
}
|
||||
|
||||
func PublicRouter(cfg *config.Config) http.Handler {
|
||||
if cfg.Environment == config.DEV {
|
||||
fmt.Println("PUBLIC DEV")
|
||||
return http.FileServer(http.Dir("./static/public"))
|
||||
}
|
||||
st, err := fs.Sub(Public, "public")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
handler := http.FileServer(http.FS(st))
|
||||
return handler
|
||||
}
|
||||
|
||||
// func Handler() http.HandlerFunc {
|
||||
// return func(w http.ResponseWriter, r *http.Request) {
|
||||
// // Get the static filesystem
|
||||
|
||||
@@ -3,13 +3,105 @@
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
:root {
|
||||
--background: oklch(0.9383 0.0042 236.4993);
|
||||
--foreground: oklch(0.3211 0 0);
|
||||
--card: oklch(1.0000 0 0);
|
||||
--card-foreground: oklch(0.3211 0 0);
|
||||
--popover: oklch(1.0000 0 0);
|
||||
--popover-foreground: oklch(0.3211 0 0);
|
||||
--primary: oklch(0.6397 0.1720 36.4421);
|
||||
--primary-foreground: oklch(1.0000 0 0);
|
||||
--secondary: oklch(0.9670 0.0029 264.5419);
|
||||
--secondary-foreground: oklch(0.4461 0.0263 256.8018);
|
||||
--muted: oklch(0.9846 0.0017 247.8389);
|
||||
--muted-foreground: oklch(0.5510 0.0234 264.3637);
|
||||
--accent: oklch(0.9119 0.0222 243.8174);
|
||||
--accent-foreground: oklch(0.3791 0.1378 265.5222);
|
||||
--destructive: oklch(0.6368 0.2078 25.3313);
|
||||
--destructive-foreground: oklch(1.0000 0 0);
|
||||
--border: oklch(0.9022 0.0052 247.8822);
|
||||
--input: oklch(0.9700 0.0029 264.5420);
|
||||
--ring: oklch(0.6397 0.1720 36.4421);
|
||||
--sidebar: oklch(0.9030 0.0046 258.3257);
|
||||
--sidebar-foreground: oklch(0.3211 0 0);
|
||||
--sidebar-primary: oklch(0.6397 0.1720 36.4421);
|
||||
--sidebar-primary-foreground: oklch(1.0000 0 0);
|
||||
--sidebar-accent: oklch(0.9119 0.0222 243.8174);
|
||||
--sidebar-accent-foreground: oklch(0.3791 0.1378 265.5222);
|
||||
--sidebar-border: oklch(0.9276 0.0058 264.5313);
|
||||
--sidebar-ring: oklch(0.6397 0.1720 36.4421);
|
||||
--font-sans: Inter, sans-serif;
|
||||
--font-serif: Source Serif 4, serif;
|
||||
--font-mono: JetBrains Mono, monospace;
|
||||
--radius: 0.75rem;
|
||||
--shadow-x: 0px;
|
||||
--shadow-y: 1px;
|
||||
--shadow-blur: 3px;
|
||||
--shadow-spread: 0px;
|
||||
--shadow-opacity: 0.1;
|
||||
--shadow-color: hsl(0 0% 0%);
|
||||
--shadow-2xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-sm: 0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow: 0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-md: 0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 2px 4px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-lg: 0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 4px 6px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 8px 10px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-2xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.25);
|
||||
--tracking-normal: 0em;
|
||||
--spacing: 0.25rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.2598 0.0306 262.6666);
|
||||
--foreground: oklch(0.9219 0 0);
|
||||
--card: oklch(0.3106 0.0301 268.6365);
|
||||
--card-foreground: oklch(0.9219 0 0);
|
||||
--popover: oklch(0.2900 0.0249 268.3986);
|
||||
--popover-foreground: oklch(0.9219 0 0);
|
||||
--primary: oklch(0.6397 0.1720 36.4421);
|
||||
--primary-foreground: oklch(1.0000 0 0);
|
||||
--secondary: oklch(0.3095 0.0266 266.7132);
|
||||
--secondary-foreground: oklch(0.9219 0 0);
|
||||
--muted: oklch(0.3095 0.0266 266.7132);
|
||||
--muted-foreground: oklch(0.7155 0 0);
|
||||
--accent: oklch(0.3380 0.0589 267.5867);
|
||||
--accent-foreground: oklch(0.8823 0.0571 254.1284);
|
||||
--destructive: oklch(0.6368 0.2078 25.3313);
|
||||
--destructive-foreground: oklch(1.0000 0 0);
|
||||
--border: oklch(0.3843 0.0301 269.7337);
|
||||
--input: oklch(0.3843 0.0301 269.7337);
|
||||
--ring: oklch(0.6397 0.1720 36.4421);
|
||||
--sidebar: oklch(0.3100 0.0283 267.7408);
|
||||
--sidebar-foreground: oklch(0.9219 0 0);
|
||||
--sidebar-primary: oklch(0.6397 0.1720 36.4421);
|
||||
--sidebar-primary-foreground: oklch(1.0000 0 0);
|
||||
--sidebar-accent: oklch(0.3380 0.0589 267.5867);
|
||||
--sidebar-accent-foreground: oklch(0.8823 0.0571 254.1284);
|
||||
--sidebar-border: oklch(0.3843 0.0301 269.7337);
|
||||
--sidebar-ring: oklch(0.6397 0.1720 36.4421);
|
||||
--font-sans: Inter, sans-serif;
|
||||
--font-serif: Source Serif 4, serif;
|
||||
--font-mono: JetBrains Mono, monospace;
|
||||
--radius: 0.75rem;
|
||||
--shadow-x: 0px;
|
||||
--shadow-y: 1px;
|
||||
--shadow-blur: 3px;
|
||||
--shadow-spread: 0px;
|
||||
--shadow-opacity: 0.1;
|
||||
--shadow-color: hsl(0 0% 0%);
|
||||
--shadow-2xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-sm: 0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow: 0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-md: 0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 2px 4px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-lg: 0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 4px 6px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.10), 0px 8px 10px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-2xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.25);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--breakpoint-3xl: 1600px;
|
||||
--breakpoint-4xl: 2000px;
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
@@ -25,54 +117,37 @@
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
}
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
|
||||
:root {
|
||||
--radius: 0.65rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
}
|
||||
--font-sans: var(--font-sans);
|
||||
--font-mono: var(--font-mono);
|
||||
--font-serif: var(--font-serif);
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
}
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
|
||||
--shadow-2xs: var(--shadow-2xs);
|
||||
--shadow-xs: var(--shadow-xs);
|
||||
--shadow-sm: var(--shadow-sm);
|
||||
--shadow: var(--shadow);
|
||||
--shadow-md: var(--shadow-md);
|
||||
--shadow-lg: var(--shadow-lg);
|
||||
--shadow-xl: var(--shadow-xl);
|
||||
--shadow-2xl: var(--shadow-2xl);
|
||||
}
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
|
||||
2
static/assets/css/output.min.css
vendored
2
static/assets/css/output.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -19,8 +19,8 @@ templ TicketRows(tickets []models.Ticket, isMobile bool) {
|
||||
<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>
|
||||
<div class="text-sm font-medium ">{ ticket.CustomerName }</div>
|
||||
<div class="text-sm text-muted-foreground ">{ ticket.CustomerEmail }</div>
|
||||
}
|
||||
@table.Cell() {
|
||||
{ ticket.ItemType }
|
||||
|
||||
@@ -121,27 +121,27 @@ func TicketRows(tickets []models.Ticket, isMobile bool) templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"text-sm font-medium text-primary-foreground\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"text-sm font-medium \">")
|
||||
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: 22, Col: 83}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/rows/ticketRows.templ`, Line: 22, Col: 60}
|
||||
}
|
||||
_, 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 \">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div><div class=\"text-sm text-muted-foreground \">")
|
||||
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: 23, Col: 49}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/partials/rows/ticketRows.templ`, Line: 23, Col: 71}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
package search
|
||||
|
||||
import "flexsupport/ui/components/input"
|
||||
import (
|
||||
"flexsupport/ui/components/input"
|
||||
|
||||
"flexsupport/ui/components/icon"
|
||||
|
||||
"flexsupport/ui/components/selectbox"
|
||||
)
|
||||
|
||||
templ SearchTickets(term, status string) {
|
||||
<span class="htmx-indicator">
|
||||
<img src="/assets/icons/loader.svg" class="size-4 animate-spin" alt="Loading..."/> Loading...
|
||||
@icon.LoaderCircle(icon.Props{Class: "animate-spin size-4"})
|
||||
</span>
|
||||
<div class="flex-1">
|
||||
@input.Input(input.Props{
|
||||
@@ -23,23 +29,55 @@ templ SearchTickets(term, status string) {
|
||||
})
|
||||
</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>
|
||||
@selectbox.SelectBox() {
|
||||
@selectbox.Trigger(selectbox.TriggerProps{
|
||||
Name: "status",
|
||||
ID: "status",
|
||||
Attributes: templ.Attributes{
|
||||
"hx-get": "/tickets",
|
||||
"value": status,
|
||||
"hx-trigger": "change",
|
||||
"hx-target": "#ticket-list",
|
||||
"hx-swap": "innerHTML",
|
||||
"hx-include": "#search",
|
||||
},
|
||||
}) {
|
||||
@selectbox.Value(selectbox.ValueProps{
|
||||
Placeholder: "Filter by status...",
|
||||
})
|
||||
}
|
||||
@selectbox.Content(selectbox.ContentProps{NoSearch: true}) {
|
||||
@selectbox.Item(selectbox.ItemProps{
|
||||
Value: "new",
|
||||
Selected: status == "new",
|
||||
}) {
|
||||
New
|
||||
}
|
||||
@selectbox.Item(selectbox.ItemProps{
|
||||
Value: "in_progress",
|
||||
Selected: status == "in_progress",
|
||||
}) {
|
||||
In Progress
|
||||
}
|
||||
@selectbox.Item(selectbox.ItemProps{
|
||||
Value: "waiting",
|
||||
Selected: status == "waiting",
|
||||
}) {
|
||||
Waiting for Parts
|
||||
}
|
||||
@selectbox.Item(selectbox.ItemProps{
|
||||
Value: "ready",
|
||||
Selected: status == "ready",
|
||||
}) {
|
||||
Ready for Pickup
|
||||
}
|
||||
@selectbox.Item(selectbox.ItemProps{
|
||||
Value: "completed",
|
||||
Selected: status == "completed",
|
||||
}) {
|
||||
Completed
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -8,7 +8,13 @@ package search
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import "flexsupport/ui/components/input"
|
||||
import (
|
||||
"flexsupport/ui/components/input"
|
||||
|
||||
"flexsupport/ui/components/icon"
|
||||
|
||||
"flexsupport/ui/components/selectbox"
|
||||
)
|
||||
|
||||
func SearchTickets(term, status string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
@@ -31,7 +37,15 @@ func SearchTickets(term, status string) templ.Component {
|
||||
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\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<span class=\"htmx-indicator\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = icon.LoaderCircle(icon.Props{Class: "animate-spin size-4"}).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</span><div class=\"flex-1\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@@ -52,85 +66,227 @@ func SearchTickets(term, status string) templ.Component {
|
||||
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=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div><div class=\"flex gap-2\">")
|
||||
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))
|
||||
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 = selectbox.Value(selectbox.ValueProps{
|
||||
Placeholder: "Filter by status...",
|
||||
}).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = selectbox.Trigger(selectbox.TriggerProps{
|
||||
Name: "status",
|
||||
ID: "status",
|
||||
Attributes: templ.Attributes{
|
||||
"hx-get": "/tickets",
|
||||
"value": status,
|
||||
"hx-trigger": "change",
|
||||
"hx-target": "#ticket-list",
|
||||
"hx-swap": "innerHTML",
|
||||
"hx-include": "#search",
|
||||
},
|
||||
}).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_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, 5, "New")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = selectbox.Item(selectbox.ItemProps{
|
||||
Value: "new",
|
||||
Selected: status == "new",
|
||||
}).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, 6, " ")
|
||||
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, 7, "In Progress")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = selectbox.Item(selectbox.ItemProps{
|
||||
Value: "in_progress",
|
||||
Selected: status == "in_progress",
|
||||
}).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_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, 9, "Waiting for Parts")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = selectbox.Item(selectbox.ItemProps{
|
||||
Value: "waiting",
|
||||
Selected: status == "waiting",
|
||||
}).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, 10, " ")
|
||||
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, 11, "Ready for Pickup")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = selectbox.Item(selectbox.ItemProps{
|
||||
Value: "ready",
|
||||
Selected: status == "ready",
|
||||
}).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, 12, " ")
|
||||
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, 13, "Completed")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = selectbox.Item(selectbox.ItemProps{
|
||||
Value: "completed",
|
||||
Selected: status == "completed",
|
||||
}).Render(templ.WithChildren(ctx, templ_7745c5c3_Var9), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = selectbox.Content(selectbox.ContentProps{NoSearch: true}).Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = selectbox.SelectBox().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, 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>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user