diff --git a/Makefile b/Makefile index 203455b..a1a58d4 100644 --- a/Makefile +++ b/Makefile @@ -20,14 +20,12 @@ tui: web: go run ./cmd/web +# Run both dev and tw in parallel for generating templates and compiling Tailwind CSS on file changes dev: @go tool templ generate -watch -cmd "go run ./cmd/web" - tw: - @npx --yes @tailwindcss/cli -i ./pkg/web/input.css -o ./pkg/web/assets/css/styles.css --watch + @npx --yes @tailwindcss/cli -i ./pkg/web/input.css -o ./pkg/web/assets/css/styles.css --watch --minify -watch: - @make -j2 dev tw completions: @go build -o ./bin/pm ./cmd/pm diff --git a/cmd/pm/main.go b/cmd/pm/main.go index 5ad70e1..d067ef4 100644 --- a/cmd/pm/main.go +++ b/cmd/pm/main.go @@ -14,6 +14,8 @@ func main() { StatisticsStoragePath: "./.pm/stats.json", } + cli := cli.NewCli() + if err := cli.Run(context.Background(), config); err != nil { return } diff --git a/cmd/survey.go b/cmd/survey.go deleted file mode 100644 index 5bd99c5..0000000 --- a/cmd/survey.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - - "github.com/LazyBachelor/LazyPM/pkg" - "github.com/LazyBachelor/LazyPM/pkg/cli" - "github.com/LazyBachelor/LazyPM/pkg/cli/repl" - "github.com/LazyBachelor/LazyPM/pkg/tui" - "github.com/LazyBachelor/LazyPM/pkg/web" -) - -func main() { - config := pkg.SurveyConfig{ - RootCmd: "pm", - WebAddress: "localhost:8080", - IssuePrefix: "pm", - BeadsDBPath: "./.pm/db.db", - StatisticsStoragePath: "./.pm/stats.json", - } - - ctx := context.Background() - var err error - - switch os.Args[1] { - case "tui": - _, err = tui.Run(ctx, config) - case "cli": - err = cli.RunWithArgs(ctx, config, os.Args[2:]) - case "repl": - err = repl.RunREPL(ctx, config) - case "web": - err = web.Run(ctx, config) - default: - err = fmt.Errorf("unknown command: %s", os.Args[1]) - } - - if err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } -} diff --git a/cmd/survey/init.go b/cmd/survey/init.go new file mode 100644 index 0000000..533976b --- /dev/null +++ b/cmd/survey/init.go @@ -0,0 +1,32 @@ +package main + +import ( + "context" + + "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/cli/repl" + "github.com/LazyBachelor/LazyPM/pkg/task" + "github.com/LazyBachelor/LazyPM/pkg/tui" + "github.com/LazyBachelor/LazyPM/pkg/web" +) + +func initializeServices(ctx context.Context) (*service.Services, func(), error) { + config := service.Config{ + IssuePrefix: "pm", + BeadsDBPath: "./.pm/db.db", + StatisticsStoragePath: "./.pm/stats.json", + WebAddress: "localhost:8080", + } + return service.NewServices(ctx, config) +} + +func initTasks() []*task.Task { + return []*task.Task{ + tasks.NewCreateIssueTask(), + } +} + +func initInterfaces() []task.Interface { + return []task.Interface{repl.NewRepl(), tui.NewTui(), web.NewWeb()} +} diff --git a/cmd/survey/survey.go b/cmd/survey/survey.go new file mode 100644 index 0000000..64e36e2 --- /dev/null +++ b/cmd/survey/survey.go @@ -0,0 +1,67 @@ +package main + +import ( + "context" + "fmt" + "log" + "math/rand" + + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/task" +) + +func main() { + ctx := context.Background() + + svc, close, err := initializeServices(ctx) + if err != nil { + log.Fatalf("Failed to initialize services: %v\n", err) + } + defer close() + + surveyTasks := initTasks() + interfaces := initInterfaces() + + if err := taskLoop(ctx, svc, surveyTasks, interfaces); err != nil { + log.Fatalf("Task loop failed: %v\n", err) + } +} + +func taskLoop(ctx context.Context, svc *service.Services, surveyTasks []*task.Task, interfaces []task.Interface) error { + interfaceIndex := rand.Int() % len(interfaces) + + for _, task := range surveyTasks { + + task.SetInterface(interfaces[interfaceIndex]) + + if err := task.Initialize(ctx, svc); err != nil { + return fmt.Errorf("failed to initialize task: %w", err) + } + + if err := task.IntroduceTask(); err != nil { + return fmt.Errorf("failed to display task introduction screen: %w", err) + } + + if err := task.StartInterface(ctx, task.Config); err != nil { + return fmt.Errorf("failed to start task interface: %w", err) + } + + ok, err := task.Validate(ctx, svc) + if err != nil { + return fmt.Errorf("validation error: %w", err) + } + if !ok { + return fmt.Errorf("task validation failed: task did not meet requirements") + } + + if err := task.StartQuestionnaire(); err != nil { + return fmt.Errorf("failed to start questionnaire: %w", err) + } + + interfaceIndex++ + if interfaceIndex >= len(interfaces) { + interfaceIndex = 0 + } + } + return nil +} diff --git a/cmd/survey/tasks/createIssue.go b/cmd/survey/tasks/createIssue.go new file mode 100644 index 0000000..9a9a2d4 --- /dev/null +++ b/cmd/survey/tasks/createIssue.go @@ -0,0 +1,84 @@ +package tasks + +import ( + "context" + "errors" + + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/task" + ui "github.com/LazyBachelor/LazyPM/pkg/task/ui" + "github.com/charmbracelet/huh" +) + +func NewCreateIssueTask() *task.Task { + aboutScreen := ui.NewTaskModel(createIssueDetails()) + questionnaire := ui.NewQuestionnaireModel(createIssueQuestionnaire()) + + task := task.NewTask(aboutScreen, questionnaire) + task.SetConfigFunc(createIssueConfig) + task.SetDbStateFunc(createIssueDbState) + task.SetValidateFunc(createIssueValidate) + return task +} + +func createIssueConfig() task.TaskConfig { + return task.TaskConfig{ + IssuePrefix: "pm", + BeadsDBPath: "./.pm/db.db", + StatisticsStoragePath: "./.pm/task-1-stats.json", + WebAddress: "localhost:8080", + } +} + +func createIssueDetails() ui.TaskDetails { + return ui.TaskDetails{ + Title: "Create Issue Task", + Description: "Create a new issue in the project management system to test the issue creation workflow.", + TimeToComplete: "15m", + Difficulty: "Hard", + } +} + +func createIssueQuestionnaire() ui.Questions { + return ui.Questions{ + huh.NewGroup( + huh.NewConfirm().Title("Was this good"), + ), + huh.NewGroup( + huh.NewSelect[int]().Options( + huh.NewOption("Very good", 1), + huh.NewOption("Very Bad", 2), + ).Title("How good was it?"), + ), + } +} + +func createIssueDbState(ctx context.Context, svc *service.Services) error { + if err := svc.DeleteIssues(); err != nil { + return err + } + + issues := []*models.Issue{ + {Title: "Test Issue", Description: "Long Description", IssueType: models.TypeBug, Status: models.StatusBlocked}, + } + + if err := svc.Beads.CreateIssues(ctx, issues, "actor"); err != nil { + return err + } + + return nil +} + +func createIssueValidate(ctx context.Context, svc *service.Services) (ok bool, errorMsg error) { + issues, err := svc.Beads.SearchIssues(ctx, "", models.IssueFilter{}) + if err != nil { + return false, err + } + + if len(issues) == 0 { + return false, errors.New("no issues found. Please create an issue to proceed.") + } + + return true, nil +} diff --git a/cmd/tui/main.go b/cmd/tui/main.go index f53021f..824e6aa 100644 --- a/cmd/tui/main.go +++ b/cmd/tui/main.go @@ -13,7 +13,9 @@ func main() { IssuePrefix: "pm", } - if _, err := tui.Run(context.Background(), config); err != nil { + tui := tui.NewTui() + + if err := tui.Run(context.Background(), config); err != nil { panic(err) } } diff --git a/cmd/web/main.go b/cmd/web/main.go index bf8542b..1cb7518 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -1,14 +1,17 @@ package main import ( - "github.com/LazyBachelor/LazyPM/internal/service" - "github.com/LazyBachelor/LazyPM/pkg/web" "context" "fmt" "os" + + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/web" ) func main() { + web := web.NewWeb() + config := service.Config{ WebAddress: "localhost:8080", BeadsDBPath: "./.pm/db.db", diff --git a/go.mod b/go.mod index 5419fc8..2d12e00 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/LazyBachelor/LazyPM go 1.25.6 require ( + charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410 github.com/google/uuid v1.6.0 github.com/muesli/reflow v0.3.0 github.com/steveyegge/beads v0.49.6 @@ -24,11 +25,14 @@ require ( require ( github.com/NYTimes/gziphandler v1.1.1 github.com/a-h/templ v0.3.977 + github.com/donseba/go-htmx v1.12.1 + github.com/go-chi/chi/v5 v5.2.5 + github.com/go-playground/form/v4 v4.3.0 + github.com/go-playground/validator/v10 v10.30.1 github.com/rs/cors v1.11.1 ) require ( - charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410 // indirect github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect github.com/andybalholm/brotli v1.2.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect @@ -51,9 +55,13 @@ require ( github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/fatih/color v1.18.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/haatos/goshipit v0.0.0-20260206030541-056850f43320 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -84,6 +92,7 @@ require ( github.com/tetratelabs/wazero v1.11.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.48.0 // indirect golang.org/x/exp v0.0.0-20260209203927-2842357ff358 // indirect golang.org/x/mod v0.33.0 // indirect golang.org/x/net v0.50.0 // indirect diff --git a/go.sum b/go.sum index d774a58..21bd467 100644 --- a/go.sum +++ b/go.sum @@ -70,6 +70,8 @@ github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfv github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/donseba/go-htmx v1.12.1 h1:ZO9TWLyZYN3KL2s/N3ZasCf/B3dmX1xzmZoIkJIT+C0= +github.com/donseba/go-htmx v1.12.1/go.mod h1:8PTAYvNKf8+QYis+DpAsggKz+sa2qljtMgvdAeNBh5s= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= @@ -80,6 +82,20 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/form/v4 v4.3.0 h1:OVttojbQv2WNCs4P+VnjPtrt/+30Ipw4890W3OaFlvk= +github.com/go-playground/form/v4 v4.3.0/go.mod h1:Cpe1iYJKoXb1vILRXEwxpWMGWyQuqplQ/4cvPecy+Jo= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -96,6 +112,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -187,6 +205,8 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20260209203927-2842357ff358 h1:kpfSV7uLwKJbFSEgNhWzGSL47NDSF/5pYYQw1V0ub6c= golang.org/x/exp v0.0.0-20260209203927-2842357ff358/go.mod h1:R3t0oliuryB5eenPWl3rrQxwnNM3WTwnsRZZiXLAAW8= golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= diff --git a/internal/service/service.go b/internal/service/service.go index f36730e..8210ae7 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -2,6 +2,7 @@ package service import ( "context" + "database/sql" "fmt" "os" "time" @@ -24,6 +25,7 @@ type Config struct { type Services struct { Config Config + DB *sql.DB Beads *BeadsService Statistics *StatisticsService } @@ -36,6 +38,12 @@ func NewServices(ctx context.Context, config Config) (*Services, func(), error) os.Exit(0) } + db, err := sql.Open("sqlite3", config.BeadsDBPath) + if err != nil { + return nil, nil, err + } + cleanupFuncs = append(cleanupFuncs, func() { db.Close() }) + store, err := beads.NewSQLiteStorage(ctx, config.BeadsDBPath) if err != nil { return nil, nil, err @@ -59,6 +67,7 @@ func NewServices(ctx context.Context, config Config) (*Services, func(), error) } return &Services{ + DB: db, Beads: beadsSvc, Statistics: statSvc, Config: config, @@ -92,3 +101,13 @@ func initialized(beadsPath string) bool { } return true } + +func (s *Services) DeleteIssues() error { + + var deleteIssues = "DELETE FROM issues;" + + if _, err := s.DB.Exec(deleteIssues); err != nil { + return err + } + return nil +} diff --git a/internal/style/styles.go b/internal/style/styles.go new file mode 100644 index 0000000..7dd47c8 --- /dev/null +++ b/internal/style/styles.go @@ -0,0 +1,28 @@ +package style + +import "github.com/charmbracelet/lipgloss" + +// Color palette +var ( + PrimaryColor = lipgloss.AdaptiveColor{Light: "#007acc", Dark: "#1e90ff"} + SecondaryColor = lipgloss.AdaptiveColor{Light: "#ff6f61", Dark: "#ff6347"} + AccentColor = lipgloss.AdaptiveColor{Light: "#6a5acd", Dark: "#9370db"} + Background = lipgloss.AdaptiveColor{Light: "#ffffff", Dark: "#1e1e1e"} + TextColor = lipgloss.AdaptiveColor{Light: "#000000", Dark: "#ffffff"} +) + +var ( + AppStyle = lipgloss.NewStyle().Padding(1, 2).Background(Background).Foreground(TextColor) +) + +var ( + DefaultBorder = lipgloss.NormalBorder() + BorderStyle = lipgloss.NewStyle().Border(DefaultBorder).BorderForeground(PrimaryColor) +) + +var ( + TitleStyle = lipgloss.NewStyle().Foreground(PrimaryColor).Bold(true) + DescriptionStyle = lipgloss.NewStyle().Foreground(TextColor).Italic(true) + DetailStyle = lipgloss.NewStyle().Foreground(SecondaryColor) + HelpStyle = lipgloss.NewStyle().Foreground(AccentColor) +) diff --git a/internal/style/themes.go b/internal/style/themes.go new file mode 100644 index 0000000..c6861bd --- /dev/null +++ b/internal/style/themes.go @@ -0,0 +1,14 @@ +package style + +import ( + "github.com/charmbracelet/huh" + "github.com/charmbracelet/lipgloss" +) + +func HuhCenterTheme() *huh.Theme { + theme := huh.ThemeBase16() + + theme.Focused.Base = lipgloss.NewStyle().Align(lipgloss.Center) + + return theme +} diff --git a/main.go b/main.go deleted file mode 100644 index 25d2bc4..0000000 --- a/main.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - - "github.com/LazyBachelor/LazyPM/internal/models" - "github.com/LazyBachelor/LazyPM/internal/service" -) - -func main() { - ctx := context.Background() - - config := service.Config{ - IssuePrefix: "pm", - BeadsDBPath: "./.pm/db.db", - StatisticsStoragePath: "./.pm/stats.json", - } - svc, cleanup, err := service.NewServices(ctx, config) - checkErr(err) - - defer cleanup() - - issue := &models.Issue{ - IssueType: models.TypeTask, - Title: "Sample Issue", - Description: "This is a sample issue created for testing.", - Status: models.StatusOpen, - } - - err = svc.Beads.CreateIssue(ctx, issue, "") - checkErr(err) - - fetchedIssues, err := svc.Beads.SearchIssues(ctx, "", models.IssueFilter{}) - checkErr(err) - - for _, iss := range fetchedIssues { - fmt.Printf("Issue ID: %s, Title: %s, Status: %s\n", iss.ID, iss.Title, iss.Status) - } - - stats, err := svc.Statistics.GetStatistics() - checkErr(err) - - fmt.Printf("\nStatistics: %v\n", stats) -} - -func checkErr(err error) { - if err != nil { - fmt.Println("Error:", err) - os.Exit(1) - } -} diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index c3f515c..27dd22a 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -11,8 +11,14 @@ import ( // CLIConfig is an alias for service.Config, used to configure the CLI. type CLIConfig = service.Config +type CLI struct{} + +func NewCli() *CLI { + return &CLI{} +} + // Run initializes the services and executes the CLI commands. -func Run(ctx context.Context, config CLIConfig) error { +func (c *CLI) Run(ctx context.Context, config CLIConfig) error { svc, cleanup, err := service.NewServices(ctx, config) if err != nil { return err @@ -30,7 +36,7 @@ func Run(ctx context.Context, config CLIConfig) error { } // RunWithArgs initializes the services and executes the CLI commands with the provided arguments. -func RunWithArgs(ctx context.Context, config CLIConfig, args []string) error { +func (c *CLI) RunWithArgs(ctx context.Context, config CLIConfig, args []string) error { svc, cleanup, err := service.NewServices(ctx, config) if err != nil { return err diff --git a/pkg/cli/repl/repl.go b/pkg/cli/repl/repl.go index cba9569..5f0faa8 100644 --- a/pkg/cli/repl/repl.go +++ b/pkg/cli/repl/repl.go @@ -22,8 +22,14 @@ You can also run shell commands directly. Type 'exit' or 'quit' to leave.` ReplTitle = "Welcome to Project Management CLI! " + ReplHelp ) -// RunREPL starts the interactive Read-Eval-Print Loop for the PM CLI. -func RunREPL(ctx context.Context, config cli.CLIConfig) error { +type REPL struct{} + +func NewRepl() *REPL { + return &REPL{} +} + +// Run starts the interactive Read-Eval-Print Loop for the PM CLI. +func (r *REPL) Run(ctx context.Context, config cli.CLIConfig) error { // Set terminal to raw mode to capture input properly in the REPL. // This allows us to handle input character by character and provide a better user experience. // We also ensure that the terminal state is restored when the REPL exits, even if an error occurs. diff --git a/pkg/survey.go b/pkg/survey.go deleted file mode 100644 index 49636e1..0000000 --- a/pkg/survey.go +++ /dev/null @@ -1,19 +0,0 @@ -package pkg - -import ( - "context" - - "github.com/LazyBachelor/LazyPM/internal/service" -) - -type SurveyConfig = service.Config - -func Run(ctx context.Context, config SurveyConfig) error { - _, cleanup, err := service.NewServices(ctx, config) - if err != nil { - return err - } - defer cleanup() - - return nil -} diff --git a/pkg/task/runner.go b/pkg/task/runner.go new file mode 100644 index 0000000..9ad8b0a --- /dev/null +++ b/pkg/task/runner.go @@ -0,0 +1,32 @@ +package task + +import ( + "context" + "fmt" + + tea "github.com/charmbracelet/bubbletea" +) + +func (t *Task) IntroduceTask() error { + if t.aboutScreen == nil { + return fmt.Errorf("aboutScreen is not set") + } + _, err := tea.NewProgram(t.aboutScreen, tea.WithAltScreen()).Run() + return err +} + +func (t *Task) StartInterface(ctx context.Context, cfg TaskConfig) error { + if t.interfaceType == nil { + return fmt.Errorf("interfaceType is not set") + } + + return t.interfaceType.Run(ctx, cfg) +} + +func (t *Task) StartQuestionnaire() error { + if t.questionnaire == nil { + return fmt.Errorf("questionnaire is not set") + } + _, err := tea.NewProgram(t.questionnaire, tea.WithAltScreen()).Run() + return err +} diff --git a/pkg/task/task.go b/pkg/task/task.go new file mode 100644 index 0000000..2a9e548 --- /dev/null +++ b/pkg/task/task.go @@ -0,0 +1,56 @@ +package task + +import ( + "context" + "fmt" + + "github.com/LazyBachelor/LazyPM/internal/service" + tea "github.com/charmbracelet/bubbletea" +) + +type Task struct { + Config TaskConfig + interfaceType Interface + aboutScreen tea.Model + questionnaire tea.Model + + validateFunc ValidateFunc + dbStateFunc DbStateFunc +} + +func NewTask(aboutScreen tea.Model, questionnaire tea.Model) *Task { + return &Task{ + aboutScreen: aboutScreen, + questionnaire: questionnaire, + } +} + +func (t *Task) SetConfigFunc(fn ConfigFunc) { + t.Config = fn() +} + +func (t *Task) SetInterface(interfaceType Interface) { + t.interfaceType = interfaceType +} + +func (t *Task) SetDbStateFunc(fn DbStateFunc) { + t.dbStateFunc = fn +} + +func (t *Task) SetValidateFunc(fn ValidateFunc) { + t.validateFunc = fn +} + +func (t *Task) Initialize(ctx context.Context, svc *service.Services) error { + if t.dbStateFunc == nil { + return fmt.Errorf("dbStateFunc is not set") + } + return t.dbStateFunc(ctx, svc) +} + +func (t *Task) Validate(ctx context.Context, svc *service.Services) (bool, error) { + if t.validateFunc == nil { + return false, fmt.Errorf("validateFunc is not set") + } + return t.validateFunc(ctx, svc) +} diff --git a/pkg/task/types.go b/pkg/task/types.go new file mode 100644 index 0000000..90427d5 --- /dev/null +++ b/pkg/task/types.go @@ -0,0 +1,17 @@ +package task + +import ( + "context" + + "github.com/LazyBachelor/LazyPM/internal/service" +) + +type TaskConfig = service.Config + +type Interface interface { + Run(context.Context, TaskConfig) error +} + +type ConfigFunc func() TaskConfig +type ValidateFunc func(context.Context, *service.Services) (ok bool, err error) +type DbStateFunc func(context.Context, *service.Services) error diff --git a/pkg/task/ui/help.go b/pkg/task/ui/help.go new file mode 100644 index 0000000..ebc31e4 --- /dev/null +++ b/pkg/task/ui/help.go @@ -0,0 +1,27 @@ +package taskui + +import "github.com/charmbracelet/bubbles/key" + +type TaskHelpKeys struct { + Quit key.Binding + Continue key.Binding +} + +var DefaultTaskKeys = TaskHelpKeys{ + Quit: key.NewBinding( + key.WithKeys("q", "ctrl+c"), + key.WithHelp("q", "Quit"), + ), + Continue: key.NewBinding( + key.WithKeys(" "), + key.WithHelp("space", "Continue"), + ), +} + +func (h TaskHelpKeys) ShortHelp() []key.Binding { + return []key.Binding{h.Continue, h.Quit} +} + +func (h TaskHelpKeys) FullHelp() [][]key.Binding { + return [][]key.Binding{{h.Continue, h.Quit}} +} diff --git a/pkg/task/ui/questionnaire.go b/pkg/task/ui/questionnaire.go new file mode 100644 index 0000000..e7dc01d --- /dev/null +++ b/pkg/task/ui/questionnaire.go @@ -0,0 +1,54 @@ +package taskui + +import ( + "charm.land/lipgloss/v2" + "github.com/LazyBachelor/LazyPM/internal/style" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/huh" +) + +func NewQuestionnaireModel(questions Questions) *QuestionnaireModel { + form := huh.NewForm(questions...). + WithTheme(style.HuhCenterTheme()).WithLayout(huh.LayoutGrid(1, 1)) + + return &QuestionnaireModel{ + Questions: questions, + form: form, + } +} + +func (q *QuestionnaireModel) Init() tea.Cmd { + return q.form.Init() +} + +func (q *QuestionnaireModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + q.SetSize(msg.Width, msg.Height) + case tea.KeyMsg: + switch msg.String() { + case "q", "ctrl+c": + return q, tea.Quit + } + } + + form, cmd := q.form.Update(msg) + if f, ok := form.(*huh.Form); ok { + q.form = f + } + return q, cmd +} + +func (q *QuestionnaireModel) View() string { + form := lipgloss.NewStyle(). + Width(q.width).Align(lipgloss.Center). + Render(q.form.View()) + + return lipgloss.Place( + q.width, q.height, lipgloss.Center, lipgloss.Center, form, + ) +} + +func (q *QuestionnaireModel) SetSize(width, height int) { + q.width, q.height = width, height +} diff --git a/pkg/task/ui/task.go b/pkg/task/ui/task.go new file mode 100644 index 0000000..c416ad3 --- /dev/null +++ b/pkg/task/ui/task.go @@ -0,0 +1,72 @@ +package taskui + +import ( + "fmt" + + "charm.land/lipgloss/v2" + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" +) + +func NewTaskModel(details TaskDetails) TaskModel { + return TaskModel{ + TaskDetails: details, + keys: DefaultTaskKeys, + help: help.New(), + } +} + +func (m TaskModel) Init() tea.Cmd { + return nil +} + +func (m TaskModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.SetSize(msg.Width, msg.Height) + case tea.KeyMsg: + switch { + case key.Matches(msg, m.keys.Quit): + return m, tea.Quit + case key.Matches(msg, m.keys.Continue): + return m, tea.Quit + } + } + return m, nil +} + +func (m TaskModel) View() string { + padding := 3 + + header := lipgloss.NewStyle(). + PaddingTop(padding).Width(m.width).Align(lipgloss.Center). + Bold(true).Render(m.Title) + + headerHeight := lipgloss.Height(header) + + helpView := lipgloss.NewStyle(). + PaddingBottom(padding). + Width(m.width).Align(lipgloss.Center). + Render(m.help.View(m.keys)) + + helpHeight := lipgloss.Height(helpView) + + detailsText := fmt.Sprintf("Time to complete: %s | Difficulty: %s", m.TimeToComplete, m.Difficulty) + details := lipgloss.NewStyle().Align(lipgloss.Center). + Width(m.width).PaddingBottom(1).Render(detailsText) + + detailsHeight := lipgloss.Height(details) + + content := lipgloss.NewStyle(). + Width(m.width).Height(m.height-headerHeight-helpHeight-detailsHeight). + Align(lipgloss.Center, lipgloss.Center). + Render(m.Description) + + return lipgloss.JoinVertical(lipgloss.Top, header, content, details, helpView) + +} + +func (m *TaskModel) SetSize(width, height int) { + m.width, m.height = width, height +} diff --git a/pkg/task/ui/types.go b/pkg/task/ui/types.go new file mode 100644 index 0000000..dc6a47e --- /dev/null +++ b/pkg/task/ui/types.go @@ -0,0 +1,28 @@ +package taskui + +import ( + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/huh" +) + +type TaskDetails struct { + Title string + Description string + TimeToComplete string + Difficulty string +} + +type TaskModel struct { + TaskDetails + keys TaskHelpKeys + help help.Model + width, height int +} + +type Questions []*huh.Group + +type QuestionnaireModel struct { + Questions + form *huh.Form + width, height int +} diff --git a/pkg/tui/tui.go b/pkg/tui/tui.go index b0feda5..f9a704d 100644 --- a/pkg/tui/tui.go +++ b/pkg/tui/tui.go @@ -10,16 +10,24 @@ import ( type TUIConfig = service.Config -func Run(ctx context.Context, config TUIConfig) (tea.Model, error) { +type Tui struct{} + +func NewTui() *Tui { + return &Tui{} +} + +func (t *Tui) Run(ctx context.Context, config TUIConfig) error { svc, cleanup, err := service.NewServices(ctx, config) if err != nil { - return nil, err + return err } defer cleanup() - app := tea.NewProgram(views.NewDashboardView(svc), - tea.WithAltScreen(), tea.WithMouseAllMotion()) + if _, err := tea.NewProgram(views.NewDashboardView(svc), + tea.WithAltScreen(), tea.WithMouseAllMotion()).Run(); err != nil { + return err + } - return app.Run() + return nil } diff --git a/pkg/web/components/base/input.templ b/pkg/web/components/base/input.templ index 87c5049..b53a849 100644 --- a/pkg/web/components/base/input.templ +++ b/pkg/web/components/base/input.templ @@ -6,6 +6,7 @@ type InputProps struct { Label string Type string // defaults to "text" Value string + Class string Placeholder string Description string Error string @@ -34,10 +35,9 @@ templ Input(props InputProps) { if props.Label != "" { { props.Label } } - + if props.Description != "" {
{ props.Description }
} diff --git a/pkg/web/components/base/input_templ.go b/pkg/web/components/base/input_templ.go index 0e5a92f..9d787ce 100644 --- a/pkg/web/components/base/input_templ.go +++ b/pkg/web/components/base/input_templ.go @@ -14,6 +14,7 @@ type InputProps struct { Label string Type string // defaults to "text" Value string + Class string Placeholder string Description string Error string @@ -70,18 +71,18 @@ func Input(props InputProps) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(props.Label) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/input.templ`, Line: 35, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/input.templ`, Line: 36, Col: 48} } _, 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, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - var templ_7745c5c3_Var3 = []any{"input validator tooltip tooltip-top", + var templ_7745c5c3_Var3 = []any{"input validator tooltip tooltip-top", props.Class, templ.KV("tooltip tooltip-top", props.DisabledMessage != ""), templ.KV("input-xs", props.Size == "xs"), templ.KV("input-sm", props.Size == "sm"), @@ -92,56 +93,43 @@ func Input(props InputProps) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, ">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if props.Description != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var13 string - templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(props.Description) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/input.templ`, Line: 79, Col: 41} - } - _, 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, 26, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - if props.Error != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(props.Error) + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(props.Description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/input.templ`, Line: 82, Col: 55} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/input.templ`, Line: 81, Col: 41} } _, 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, 28, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - if props.ValidatorHint != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var15 string - templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(props.ValidatorHint) + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(props.Error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/input.templ`, Line: 85, Col: 80} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/input.templ`, Line: 84, Col: 55} } _, 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, 30, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "") + if props.ValidatorHint != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/pkg/web/components/base/range.templ b/pkg/web/components/base/range.templ index d52e7de..b22a39f 100644 --- a/pkg/web/components/base/range.templ +++ b/pkg/web/components/base/range.templ @@ -17,7 +17,8 @@ type RangeProps struct { templ Range(props RangeProps) {
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" step=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", props.Step)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/range.templ`, Line: 48, Col: 40} + } + _, 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, 13, "\">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/pkg/web/components/base/select.templ b/pkg/web/components/base/select.templ index 9a14c81..6dc7088 100644 --- a/pkg/web/components/base/select.templ +++ b/pkg/web/components/base/select.templ @@ -33,10 +33,9 @@ templ Select(props SelectProps) { templ.KV("select-lg", props.Size == "lg"), templ.KV("select-xl", props.Size == "xl"), } - if props.ID != "" { - id={ props.ID } - } + id={ props.Name } name={ props.Name } + aria-label={ props.Label } if props.Required { required } diff --git a/pkg/web/components/base/select_templ.go b/pkg/web/components/base/select_templ.go index 121dbe8..aab85ee 100644 --- a/pkg/web/components/base/select_templ.go +++ b/pkg/web/components/base/select_templ.go @@ -95,48 +95,51 @@ func Select(props SelectProps) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" id=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if props.ID != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " id=\"") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var5 string - templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(props.ID) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/select.templ`, Line: 37, Col: 17} - } - _, 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, 7, "\"") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(props.Name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/select.templ`, Line: 36, Col: 18} } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " name=\"") + _, 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, "\" name=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(props.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/select.templ`, Line: 39, Col: 20} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/select.templ`, Line: 37, Col: 20} } _, 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, 9, "\"") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" aria-label=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(props.Label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/select.templ`, Line: 38, Col: 27} + } + _, 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, "\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if props.Required { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, " required") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, " required") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -145,7 +148,7 @@ func Select(props SelectProps) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, ">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, ">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -153,30 +156,30 @@ func Select(props SelectProps) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if props.Description != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var7 string - templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(props.Description) + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(props.Description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/select.templ`, Line: 48, Col: 42} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/select.templ`, Line: 47, Col: 42} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + _, 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, 14, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -200,55 +203,55 @@ func SelectOptions(options []SelectOption) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var8 := templ.GetChildren(ctx) - if templ_7745c5c3_Var8 == nil { - templ_7745c5c3_Var8 = templ.NopComponent + templ_7745c5c3_Var9 := templ.GetChildren(ctx) + if templ_7745c5c3_Var9 == nil { + templ_7745c5c3_Var9 = templ.NopComponent } ctx = templ.ClearChildren(ctx) for i := range options { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, " value=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(options[i].Label) + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(options[i].Value) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/select.templ`, Line: 64, Col: 21} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/select.templ`, Line: 61, Col: 27} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(options[i].Label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/select.templ`, Line: 63, Col: 21} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/pkg/web/components/base/textarea.templ b/pkg/web/components/base/textarea.templ index f065a4f..ce501c7 100644 --- a/pkg/web/components/base/textarea.templ +++ b/pkg/web/components/base/textarea.templ @@ -24,10 +24,9 @@ templ Textarea(props TextareaProps) { }