diff --git a/apps/cli/main.go b/apps/cli/main.go index 3729f64..839a9bc 100644 --- a/apps/cli/main.go +++ b/apps/cli/main.go @@ -1,17 +1,18 @@ package main import ( - "os" - + "fmt" "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/menu" + "marmic/servicetrade-toolbox/internal/handlers/cli" "marmic/servicetrade-toolbox/internal/ui" + "marmic/servicetrade-toolbox/internal/utils" + "os" ) func main() { ui.DisplayStartScreen() - email, password, err := ui.PromptCredentials() + email, password, err := utils.PromptCredentials() if err != nil { ui.DisplayError("Error getting credentials:", err) os.Exit(1) @@ -24,23 +25,71 @@ func main() { os.Exit(1) } - ui.DisplayMessage("Login successful!") + fmt.Println("Login successful!") - mainMenu := menu.GetMainMenu() for { - choice := menu.DisplayMenuAndGetChoice(mainMenu, "Main Menu") - if choice == len(mainMenu)+1 { - ui.ClearScreen() - ui.DisplayMessage("Logging out...") + ui.ClearScreen() + fmt.Println("Main Menu:") + fmt.Println("1. Jobs") + fmt.Println("2. Invoices") + fmt.Println("3. Companies") + fmt.Println("4. Assets") + fmt.Println("5. Contacts") + fmt.Println("6. Contracts") + fmt.Println("7. Generic Tools") + fmt.Println("8. Locations") + fmt.Println("9. Notifications") + fmt.Println("10. Quotes") + fmt.Println("11. Services") + fmt.Println("12. Tags") + fmt.Println("13. Users") + fmt.Println("14. Admin") + fmt.Println("15. Logout") + + choice, err := utils.GetUserChoice(15) + if err != nil { + ui.DisplayError("Invalid input:", err) + utils.PressEnterToContinue() + continue + } + switch choice { + case 1: + cli.HandleJobs(session) + case 2: + cli.HandleInvoices(session) + case 3: + cli.HandleCompanies(session) + case 4: + cli.HandleAssets(session) + case 5: + cli.HandleContacts(session) + case 6: + cli.HandleContracts(session) + case 7: + cli.HandleGenericTools(session) + case 8: + cli.HandleLocations(session) + case 9: + cli.HandleNotifications(session) + case 10: + cli.HandleQuotes(session) + case 11: + cli.HandleServices(session) + case 12: + cli.HandleTags(session) + case 13: + cli.HandleUsers(session) + case 14: + cli.HandleAdmin(session) + case 15: err := session.Logout() if err != nil { ui.DisplayError("Error during logout: ", err) } else { - ui.DisplayMessage("Logout successful.") + fmt.Println("Logout successful.") } - ui.DisplayMessage("Exiting ServiceTrade CLI Toolbox. Goodbye!") + fmt.Println("Exiting ServiceTrade CLI Toolbox. Goodbye!") return } - mainMenu[choice-1].Handler(session) } } diff --git a/apps/web/main.go b/apps/web/main.go index d02642e..b83466b 100644 --- a/apps/web/main.go +++ b/apps/web/main.go @@ -4,7 +4,7 @@ import ( "log" "net/http" - "marmic/servicetrade-toolbox/internal/handlers" + "marmic/servicetrade-toolbox/internal/handlers/web" "marmic/servicetrade-toolbox/internal/middleware" "github.com/gorilla/mux" @@ -17,28 +17,28 @@ func main() { r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) // Auth routes - r.HandleFunc("/login", handlers.LoginHandler).Methods("GET", "POST") - r.HandleFunc("/logout", handlers.LogoutHandler).Methods("GET", "POST") + r.HandleFunc("/login", web.LoginHandler).Methods("GET", "POST") + r.HandleFunc("/logout", web.LogoutHandler).Methods("GET", "POST") // Protected routes protected := r.PathPrefix("/").Subrouter() protected.Use(middleware.AuthMiddleware) - protected.HandleFunc("/", handlers.DashboardHandler).Methods("GET") - protected.HandleFunc("/jobs", handlers.JobsHandler).Methods("GET") - protected.HandleFunc("/admin", handlers.AdminHandler).Methods("GET") - protected.HandleFunc("/assets", handlers.AssetsHandler).Methods("GET") - protected.HandleFunc("/companies", handlers.CompaniesHandler).Methods("GET") - protected.HandleFunc("/contacts", handlers.ContactsHandler).Methods("GET") - protected.HandleFunc("/contracts", handlers.ContractsHandler).Methods("GET") - protected.HandleFunc("/generic", handlers.GenericHandler).Methods("GET") - protected.HandleFunc("/invoices", handlers.InvoicesHandler).Methods("GET", "POST") - protected.HandleFunc("/locations", handlers.LocationsHandler).Methods("GET") - protected.HandleFunc("/notifications", handlers.NotificationsHandler).Methods("GET") - protected.HandleFunc("/quotes", handlers.QuotesHandler).Methods("GET") - protected.HandleFunc("/services", handlers.ServicesHandler).Methods("GET") - protected.HandleFunc("/tags", handlers.TagsHandler).Methods("GET") - protected.HandleFunc("/users", handlers.UsersHandler).Methods("GET") + protected.HandleFunc("/", web.DashboardHandler).Methods("GET") + protected.HandleFunc("/jobs", web.JobsHandler).Methods("GET") + protected.HandleFunc("/invoices", web.InvoicesHandler).Methods("GET", "POST") + protected.HandleFunc("/admin", web.AdminHandler).Methods("GET") + protected.HandleFunc("/assets", web.AssetsHandler).Methods("GET") + protected.HandleFunc("/companies", web.CompaniesHandler).Methods("GET") + protected.HandleFunc("/contacts", web.ContactsHandler).Methods("GET") + protected.HandleFunc("/contracts", web.ContractsHandler).Methods("GET") + protected.HandleFunc("/generic", web.GenericHandler).Methods("GET") + protected.HandleFunc("/locations", web.LocationsHandler).Methods("GET") + protected.HandleFunc("/notifications", web.NotificationsHandler).Methods("GET") + protected.HandleFunc("/quotes", web.QuotesHandler).Methods("GET") + protected.HandleFunc("/services", web.ServicesHandler).Methods("GET") + protected.HandleFunc("/tags", web.TagsHandler).Methods("GET") + protected.HandleFunc("/users", web.UsersHandler).Methods("GET") log.Println("Server starting on :8080") log.Fatal(http.ListenAndServe(":8080", r)) diff --git a/internal/api/api.go b/internal/api/api.go index 7152b1c..0722531 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -6,12 +6,11 @@ import ( "fmt" "io" "net/http" - "regexp" - "strconv" - "strings" "time" ) +const BaseURL = "https://api.servicetrade.com/api" + type Session struct { Client *http.Client Cookie string @@ -26,7 +25,7 @@ func NewSession() *Session { } func (s *Session) ValidateSession() error { - url := "https://api.servicetrade.com/api/auth/validate" + url := fmt.Sprintf("%s/auth/validate", BaseURL) req, err := http.NewRequest("GET", url, nil) if err != nil { return err @@ -47,61 +46,54 @@ func (s *Session) ValidateSession() error { return nil } +func (s *Session) DoRequest(method, endpoint string, body io.Reader) (*http.Response, error) { + url := fmt.Sprintf("%s%s", BaseURL, endpoint) + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, fmt.Errorf("error creating request: %v", err) + } + req.Header.Set("Cookie", s.Cookie) + req.Header.Set("Content-Type", "application/json") + + return s.Client.Do(req) +} + func (s *Session) Login(email, password string) error { - url := "https://api.servicetrade.com/api/auth" payload := map[string]string{ "username": email, "password": password, } payloadBytes, _ := json.Marshal(payload) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(payloadBytes)) + resp, err := s.DoRequest("POST", "/auth", bytes.NewBuffer(payloadBytes)) if err != nil { - return fmt.Errorf("error creating request: %v", err) - } - req.Header.Set("Content-Type", "application/json") - - resp, err := s.Client.Do(req) - if err != nil { - return fmt.Errorf("error sending request: %v", err) + return err } defer resp.Body.Close() - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("failed to authenticate: %s, response: %s", resp.Status, string(body)) } for _, cookie := range resp.Cookies() { - if strings.Contains(cookie.Name, "PHPSESSID") { + if cookie.Name == "PHPSESSID" { s.Cookie = cookie.String() - fmt.Println(s.Cookie) return nil } } - return fmt.Errorf("failed to retrieve session cookie; authentication may have failed") + return fmt.Errorf("failed to retrieve session cookie") } func (s *Session) Logout() error { - if s.Cookie == "" { - return fmt.Errorf("no active session to end") - } - - url := "https://api.servicetrade.com/api/auth" - req, err := http.NewRequest("DELETE", url, nil) - if err != nil { - return fmt.Errorf("failed to create DELETE request to end session: %v", err) - } - req.Header.Set("Cookie", s.Cookie) - - resp, err := s.Client.Do(req) + resp, err := s.DoRequest("DELETE", "/auth", nil) if err != nil { - return fmt.Errorf("failed to send DELETE request to end session: %v", err) + return err } defer resp.Body.Close() - if resp.StatusCode != 200 && resp.StatusCode != 204 { + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("failed to end session: %s, response: %s", resp.Status, string(body)) } @@ -109,171 +101,3 @@ func (s *Session) Logout() error { s.Cookie = "" return nil } - -func (s *Session) GetAttachmentsForJob(jobID string) (map[string]interface{}, error) { - url := fmt.Sprintf("https://api.servicetrade.com/api/job/%s/paperwork", jobID) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %v", err) - } - req.Header.Set("Cookie", s.Cookie) - - resp, err := s.Client.Do(req) - if err != nil { - return nil, fmt.Errorf("error sending request: %v", err) - } - defer resp.Body.Close() - - body, _ := io.ReadAll(resp.Body) - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("failed to get attachments: %s, response: %s", resp.Status, string(body)) - } - - var result map[string]interface{} - if err := json.Unmarshal(body, &result); err != nil { - return nil, fmt.Errorf("error unmarshalling response: %v, body: %s", err, string(body)) - } - return result, nil -} - -func (s *Session) DeleteAttachment(endpoint string) error { - req, err := http.NewRequest("DELETE", endpoint, nil) - if err != nil { - return fmt.Errorf("failed to create DELETE request: %v", err) - } - req.Header.Set("Cookie", s.Cookie) - - resp, err := s.Client.Do(req) - if err != nil { - return fmt.Errorf("failed to send DELETE request: %v", err) - } - defer resp.Body.Close() - - if resp.StatusCode != 200 && resp.StatusCode != 204 { - body, _ := io.ReadAll(resp.Body) - return fmt.Errorf("failed to delete attachment: %s, response: %s", resp.Status, string(body)) - } - - return nil -} - -func (s *Session) GetDeficiencyInfoForJob(jobID string) ([][]string, error) { - url := fmt.Sprintf("https://api.servicetrade.com/api/deficiency/%s", jobID) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %v", err) - } - req.Header.Set("Cookie", s.Cookie) - - resp, err := s.Client.Do(req) - if err != nil { - return nil, fmt.Errorf("error sending request: %v", err) - } - defer resp.Body.Close() - - body, _ := io.ReadAll(resp.Body) - if resp.StatusCode != 200 { - return nil, fmt.Errorf("failed to get deficiency info: %s, response: %s", resp.Status, string(body)) - } - - var result map[string]interface{} - if err := json.Unmarshal(body, &result); err != nil { - return nil, fmt.Errorf("error unmarshalling response: %v, body: %s", err, string(body)) - } - - var deficiencyLogs [][]string - if data, ok := result["data"].([]interface{}); ok { - for _, item := range data { - if deficiency, ok := item.(map[string]interface{}); ok { - id := fmt.Sprintf("%v", deficiency["id"]) - description := fmt.Sprintf("%v", deficiency["description"]) - status := fmt.Sprintf("%v", deficiency["status"]) - deficiencyLogs = append(deficiencyLogs, []string{jobID, id, description, status}) - } - } - } - - return deficiencyLogs, nil -} - -func (s *Session) GetDeficiencyById(deficiencyId string) (map[string]interface{}, error) { - url := fmt.Sprintf("https://api.servicetrade.com/api/deficiency/%s", deficiencyId) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %v", err) - } - req.Header.Set("Cookie", s.Cookie) - - resp, err := s.Client.Do(req) - if err != nil { - return nil, fmt.Errorf("error sending request: %v", err) - } - defer resp.Body.Close() - - body, _ := io.ReadAll(resp.Body) - if resp.StatusCode != 200 { - return nil, fmt.Errorf("failed to get deficiency info: %s, response: %s", resp.Status, string(body)) - } - - var result map[string]interface{} - if err := json.Unmarshal(body, &result); err != nil { - return nil, fmt.Errorf("error unmarshalling response: %v, body: %s", err, string(body)) - } - - return result, nil -} - -func (s *Session) GetInvoice(identifier string) (map[string]interface{}, error) { - var url string - - // Regular expression to check if the identifier starts with a letter - isInvoiceNumber, _ := regexp.MatchString(`^[A-Za-z]`, identifier) - - if isInvoiceNumber { - url = fmt.Sprintf("https://api.servicetrade.com/api/invoice?invoiceNumber=%s", identifier) - } else { - // Check if the identifier is a valid number - if _, err := strconv.Atoi(identifier); err != nil { - return nil, fmt.Errorf("invalid invoice identifier: %s", identifier) - } - url = fmt.Sprintf("https://api.servicetrade.com/api/invoice/%s", identifier) - } - - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %v", err) - } - req.Header.Set("Cookie", s.Cookie) - - resp, err := s.Client.Do(req) - if err != nil { - return nil, fmt.Errorf("error sending request: %v", err) - } - defer resp.Body.Close() - - body, _ := io.ReadAll(resp.Body) - if resp.StatusCode != 200 { - return nil, fmt.Errorf("failed to get invoice info: %s, response: %s", resp.Status, string(body)) - } - - var result map[string]interface{} - if err := json.Unmarshal(body, &result); err != nil { - return nil, fmt.Errorf("error unmarshalling response: %v, body: %s", err, string(body)) - } - - // Check if the response contains a 'data' field - if data, ok := result["data"].(map[string]interface{}); ok { - // If 'invoices' field exists, it's a search by invoice number - if invoices, ok := data["invoices"].([]interface{}); ok && len(invoices) > 0 { - if invoice, ok := invoices[0].(map[string]interface{}); ok { - return invoice, nil - } - } else { - // If 'invoices' doesn't exist, it's a direct invoice lookup by ID - return data, nil - } - } - - return nil, fmt.Errorf("no invoice found in the response") -} diff --git a/internal/api/invoices.go b/internal/api/invoices.go index e69de29..063df4a 100644 --- a/internal/api/invoices.go +++ b/internal/api/invoices.go @@ -0,0 +1,48 @@ +package api + +import ( + "encoding/json" + "fmt" + "io" + "regexp" +) + +func (s *Session) GetInvoice(identifier string) (map[string]interface{}, error) { + var endpoint string + + isInvoiceNumber, _ := regexp.MatchString(`^[A-Za-z]`, identifier) + + if isInvoiceNumber { + endpoint = fmt.Sprintf("/invoice?invoiceNumber=%s", identifier) + } else { + endpoint = fmt.Sprintf("/invoice/%s", identifier) + } + + resp, err := s.DoRequest("GET", endpoint, nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to get invoice info: %s, response: %s", resp.Status, string(body)) + } + + var result map[string]interface{} + if err := json.Unmarshal(body, &result); err != nil { + return nil, fmt.Errorf("error unmarshalling response: %v", err) + } + + if data, ok := result["data"].(map[string]interface{}); ok { + if invoices, ok := data["invoices"].([]interface{}); ok && len(invoices) > 0 { + if invoice, ok := invoices[0].(map[string]interface{}); ok { + return invoice, nil + } + } else { + return data, nil + } + } + + return nil, fmt.Errorf("no invoice found in the response") +} diff --git a/internal/api/jobs.go b/internal/api/jobs.go index e69de29..b64c681 100644 --- a/internal/api/jobs.go +++ b/internal/api/jobs.go @@ -0,0 +1,63 @@ +package api + +import ( + "encoding/json" + "fmt" + "io" +) + +func (s *Session) GetAttachmentsForJob(jobID string) (map[string]interface{}, error) { + resp, err := s.DoRequest("GET", fmt.Sprintf("/job/%s/paperwork", jobID), nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to get attachments: %s, response: %s", resp.Status, string(body)) + } + + var result map[string]interface{} + if err := json.Unmarshal(body, &result); err != nil { + return nil, fmt.Errorf("error unmarshalling response: %v", err) + } + return result, nil +} + +func (s *Session) DeleteAttachment(attachmentID string) error { + resp, err := s.DoRequest("DELETE", fmt.Sprintf("/attachment/%s", attachmentID), nil) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 && resp.StatusCode != 204 { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("failed to delete attachment: %s, response: %s", resp.Status, string(body)) + } + + return nil +} + +func (s *Session) GetDeficiencyInfoForJob(jobID string) ([]map[string]interface{}, error) { + resp, err := s.DoRequest("GET", fmt.Sprintf("/deficiency/%s", jobID), nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to get deficiency info: %s, response: %s", resp.Status, string(body)) + } + + var result struct { + Data []map[string]interface{} `json:"data"` + } + if err := json.Unmarshal(body, &result); err != nil { + return nil, fmt.Errorf("error unmarshalling response: %v", err) + } + + return result.Data, nil +} diff --git a/internal/api/session_store.go b/internal/api/session_store.go new file mode 100644 index 0000000..425a3b9 --- /dev/null +++ b/internal/api/session_store.go @@ -0,0 +1,46 @@ +package api + +import ( + "sync" + "time" +) + +type SessionStore struct { + sessions map[string]*Session + mu sync.RWMutex +} + +func NewSessionStore() *SessionStore { + return &SessionStore{ + sessions: make(map[string]*Session), + } +} + +func (store *SessionStore) Set(sessionID string, session *Session) { + store.mu.Lock() + defer store.mu.Unlock() + store.sessions[sessionID] = session +} + +func (store *SessionStore) Get(sessionID string) (*Session, bool) { + store.mu.RLock() + defer store.mu.RUnlock() + session, ok := store.sessions[sessionID] + return session, ok +} + +func (store *SessionStore) Delete(sessionID string) { + store.mu.Lock() + defer store.mu.Unlock() + delete(store.sessions, sessionID) +} + +func (store *SessionStore) CleanupSessions() { + store.mu.Lock() + defer store.mu.Unlock() + for id, session := range store.sessions { + if time.Since(session.LastAccessed) > 24*time.Hour { + delete(store.sessions, id) + } + } +} diff --git a/internal/handlers/cli/admin.go b/internal/handlers/cli/admin.go index 2ca106b..379e598 100644 --- a/internal/handlers/cli/admin.go +++ b/internal/handlers/cli/admin.go @@ -1,24 +1,48 @@ -package handlers +package cli import ( - "html/template" - "net/http" + "marmic/servicetrade-toolbox/internal/api" + "marmic/servicetrade-toolbox/internal/ui" + "marmic/servicetrade-toolbox/internal/utils" ) -func AdminHandler(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("HX-Request") == "true" { - // This is an HTMX request, return only the jobs partial - tmpl := template.Must(template.ParseFiles("templates/partials/jobs.html")) - jobs := r.Cookies() // Replace with actual data fetching - tmpl.Execute(w, jobs) - } else { - // This is a full page request - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/admin.html")) - jobs := []string{"Job 1", "Job 2", "Job 3"} // Replace with actual data fetching +func HandleAdmin(session *api.Session) { + for { + ui.ClearScreen() + ui.DisplayMenu([]string{ + "User Management", + "System Settings", + "Back to Main Menu", + }, "Admin Menu") - tmpl.Execute(w, map[string]interface{}{ - "Title": "Jobs", - "Jobs": jobs, - }) + choice, err := utils.GetUserChoice(3) + if err != nil { + ui.DisplayError("Invalid input:", err) + utils.PressEnterToContinue() + continue + } + + switch choice { + case 1: + userManagement(session) + case 2: + systemSettings(session) + case 3: + return + } } } + +func userManagement(session *api.Session) { + ui.ClearScreen() + ui.DisplayMessage("User Management") + // TODO: Implement user management logic + utils.PressEnterToContinue() +} + +func systemSettings(session *api.Session) { + ui.ClearScreen() + ui.DisplayMessage("System Settings") + // TODO: Implement system settings logic + utils.PressEnterToContinue() +} diff --git a/internal/handlers/cli/assets.go b/internal/handlers/cli/assets.go index 1f7d07f..3ee5ce0 100644 --- a/internal/handlers/cli/assets.go +++ b/internal/handlers/cli/assets.go @@ -1,11 +1,67 @@ -package handlers +package cli import ( - "html/template" - "net/http" + "fmt" + "marmic/servicetrade-toolbox/internal/api" + "marmic/servicetrade-toolbox/internal/ui" + "marmic/servicetrade-toolbox/internal/utils" ) -func AssetsHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/assets.html")) - tmpl.Execute(w, nil) +func HandleAssets(session *api.Session) { + for { + ui.ClearScreen() + fmt.Println("Assets Menu:") + fmt.Println("1. List Assets") + fmt.Println("2. Add Asset") + fmt.Println("3. Edit Asset") + fmt.Println("4. Delete Asset") + fmt.Println("5. Back to Main Menu") + + choice, err := utils.GetUserChoice(5) + if err != nil { + ui.DisplayError("Invalid input:", err) + utils.PressEnterToContinue() + continue + } + switch choice { + case 1: + listAssets(session) + case 2: + addAsset(session) + case 3: + editAsset(session) + case 4: + deleteAsset(session) + case 5: + return + } + } +} + +func listAssets(session *api.Session) { + ui.ClearScreen() + fmt.Println("Listing assets...") + // TODO: Implement asset listing logic using the API + utils.PressEnterToContinue() +} + +func addAsset(session *api.Session) { + ui.ClearScreen() + fmt.Println("Adding a new asset...") + // TODO: Implement asset creation logic using the API + utils.PressEnterToContinue() +} + +func editAsset(session *api.Session) { + ui.ClearScreen() + fmt.Println("Editing an asset...") + // TODO: Implement asset editing logic using the API + utils.PressEnterToContinue() +} + +func deleteAsset(session *api.Session) { + ui.ClearScreen() + fmt.Println("Deleting an asset...") + // TODO: Implement asset deletion logic using the API + utils.PressEnterToContinue() } diff --git a/internal/handlers/cli/companies.go b/internal/handlers/cli/companies.go index 744f039..c37e441 100644 --- a/internal/handlers/cli/companies.go +++ b/internal/handlers/cli/companies.go @@ -1,11 +1,67 @@ -package handlers +package cli import ( - "html/template" - "net/http" + "fmt" + "marmic/servicetrade-toolbox/internal/api" + "marmic/servicetrade-toolbox/internal/ui" + "marmic/servicetrade-toolbox/internal/utils" ) -func CompaniesHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/companies.html")) - tmpl.Execute(w, nil) +func HandleCompanies(session *api.Session) { + for { + ui.ClearScreen() + fmt.Println("Companies Menu:") + fmt.Println("1. List Companies") + fmt.Println("2. Add Company") + fmt.Println("3. Edit Company") + fmt.Println("4. Delete Company") + fmt.Println("5. Back to Main Menu") + + choice, err := utils.GetUserChoice(5) + if err != nil { + ui.DisplayError("Invalid input:", err) + utils.PressEnterToContinue() + continue + } + switch choice { + case 1: + listCompanies(session) + case 2: + addCompany(session) + case 3: + editCompany(session) + case 4: + deleteCompany(session) + case 5: + return + } + } +} + +func listCompanies(session *api.Session) { + ui.ClearScreen() + fmt.Println("Listing companies...") + // TODO: Implement company listing logic using the API + utils.PressEnterToContinue() +} + +func addCompany(session *api.Session) { + ui.ClearScreen() + fmt.Println("Adding a new company...") + // TODO: Implement company creation logic using the API + utils.PressEnterToContinue() +} + +func editCompany(session *api.Session) { + ui.ClearScreen() + fmt.Println("Editing a company...") + // TODO: Implement company editing logic using the API + utils.PressEnterToContinue() +} + +func deleteCompany(session *api.Session) { + ui.ClearScreen() + fmt.Println("Deleting a company...") + // TODO: Implement company deletion logic using the API + utils.PressEnterToContinue() } diff --git a/internal/handlers/cli/contacts.go b/internal/handlers/cli/contacts.go index 46addef..ced6f97 100644 --- a/internal/handlers/cli/contacts.go +++ b/internal/handlers/cli/contacts.go @@ -1,11 +1,67 @@ -package handlers +package cli import ( - "html/template" - "net/http" + "fmt" + "marmic/servicetrade-toolbox/internal/api" + "marmic/servicetrade-toolbox/internal/ui" + "marmic/servicetrade-toolbox/internal/utils" ) -func ContactsHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/contacts.html")) - tmpl.Execute(w, nil) +func HandleContacts(session *api.Session) { + for { + ui.ClearScreen() + fmt.Println("Contacts Menu:") + fmt.Println("1. List Contacts") + fmt.Println("2. Add Contact") + fmt.Println("3. Edit Contact") + fmt.Println("4. Delete Contact") + fmt.Println("5. Back to Main Menu") + + choice, err := utils.GetUserChoice(5) + if err != nil { + ui.DisplayError("Invalid input:", err) + utils.PressEnterToContinue() + continue + } + switch choice { + case 1: + listContacts(session) + case 2: + addContact(session) + case 3: + editContact(session) + case 4: + deleteContact(session) + case 5: + return + } + } +} + +func listContacts(session *api.Session) { + ui.ClearScreen() + fmt.Println("Listing contacts...") + // TODO: Implement contact listing logic using the API + utils.PressEnterToContinue() +} + +func addContact(session *api.Session) { + ui.ClearScreen() + fmt.Println("Adding a new contact...") + // TODO: Implement contact creation logic using the API + utils.PressEnterToContinue() +} + +func editContact(session *api.Session) { + ui.ClearScreen() + fmt.Println("Editing a contact...") + // TODO: Implement contact editing logic using the API + utils.PressEnterToContinue() +} + +func deleteContact(session *api.Session) { + ui.ClearScreen() + fmt.Println("Deleting a contact...") + // TODO: Implement contact deletion logic using the API + utils.PressEnterToContinue() } diff --git a/internal/handlers/cli/contracts.go b/internal/handlers/cli/contracts.go index 20c3ccd..05c8e40 100644 --- a/internal/handlers/cli/contracts.go +++ b/internal/handlers/cli/contracts.go @@ -1,11 +1,67 @@ -package handlers +package cli import ( - "html/template" - "net/http" + "fmt" + "marmic/servicetrade-toolbox/internal/api" + "marmic/servicetrade-toolbox/internal/ui" + "marmic/servicetrade-toolbox/internal/utils" ) -func ContractsHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/contracts.html")) - tmpl.Execute(w, nil) +func HandleContracts(session *api.Session) { + for { + ui.ClearScreen() + fmt.Println("Contracts Menu:") + fmt.Println("1. List Contracts") + fmt.Println("2. Add Contract") + fmt.Println("3. Edit Contract") + fmt.Println("4. Delete Contract") + fmt.Println("5. Back to Main Menu") + + choice, err := utils.GetUserChoice(5) + if err != nil { + ui.DisplayError("Invalid input:", err) + utils.PressEnterToContinue() + continue + } + switch choice { + case 1: + listContracts(session) + case 2: + addContract(session) + case 3: + editContract(session) + case 4: + deleteContract(session) + case 5: + return + } + } +} + +func listContracts(session *api.Session) { + ui.ClearScreen() + fmt.Println("Listing contracts...") + // TODO: Implement contract listing logic using the API + utils.PressEnterToContinue() +} + +func addContract(session *api.Session) { + ui.ClearScreen() + fmt.Println("Adding a new contract...") + // TODO: Implement contract creation logic using the API + utils.PressEnterToContinue() +} + +func editContract(session *api.Session) { + ui.ClearScreen() + fmt.Println("Editing a contract...") + // TODO: Implement contract editing logic using the API + utils.PressEnterToContinue() +} + +func deleteContract(session *api.Session) { + ui.ClearScreen() + fmt.Println("Deleting a contract...") + // TODO: Implement contract deletion logic using the API + utils.PressEnterToContinue() } diff --git a/internal/handlers/cli/dashboard.go b/internal/handlers/cli/dashboard.go deleted file mode 100644 index ecd687a..0000000 --- a/internal/handlers/cli/dashboard.go +++ /dev/null @@ -1,34 +0,0 @@ -package handlers - -import ( - "html/template" - "log" - "net/http" -) - -func DashboardHandler(w http.ResponseWriter, r *http.Request) { - tmpl, err := template.ParseFiles( - "templates/layout.html", - "templates/dashboard.html", - "templates/partials/invoice_search.html", - "templates/partials/invoice_search_results.html", - ) - if err != nil { - log.Printf("Template parsing error: %v", err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return - } - - data := struct{}{} // Empty struct as data - - if r.Header.Get("HX-Request") == "true" { - err = tmpl.ExecuteTemplate(w, "content", data) - } else { - err = tmpl.ExecuteTemplate(w, "layout.html", data) - } - if err != nil { - log.Printf("Template execution error: %v", err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return - } -} diff --git a/internal/handlers/cli/generic.go b/internal/handlers/cli/generic.go index efe5640..ef1bab0 100644 --- a/internal/handlers/cli/generic.go +++ b/internal/handlers/cli/generic.go @@ -1,11 +1,57 @@ -package handlers +package cli import ( - "html/template" - "net/http" + "fmt" + "marmic/servicetrade-toolbox/internal/api" + "marmic/servicetrade-toolbox/internal/ui" + "marmic/servicetrade-toolbox/internal/utils" ) -func GenericHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/generic.html")) - tmpl.Execute(w, nil) +func HandleGenericTools(session *api.Session) { + for { + ui.ClearScreen() + fmt.Println("Generic Tools Menu:") + fmt.Println("1. Tool 1") + fmt.Println("2. Tool 2") + fmt.Println("3. Tool 3") + fmt.Println("4. Back to Main Menu") + + choice, err := utils.GetUserChoice(4) + if err != nil { + ui.DisplayError("Invalid input:", err) + utils.PressEnterToContinue() + continue + } + switch choice { + case 1: + runTool1(session) + case 2: + runTool2(session) + case 3: + runTool3(session) + case 4: + return + } + } +} + +func runTool1(session *api.Session) { + ui.ClearScreen() + fmt.Println("Running Tool 1...") + // TODO: Implement Tool 1 logic + utils.PressEnterToContinue() +} + +func runTool2(session *api.Session) { + ui.ClearScreen() + fmt.Println("Running Tool 2...") + // TODO: Implement Tool 2 logic + utils.PressEnterToContinue() +} + +func runTool3(session *api.Session) { + ui.ClearScreen() + fmt.Println("Running Tool 3...") + // TODO: Implement Tool 3 logic + utils.PressEnterToContinue() } diff --git a/internal/handlers/cli/invoices.go b/internal/handlers/cli/invoices.go index 3e6e0bd..752ed07 100644 --- a/internal/handlers/cli/invoices.go +++ b/internal/handlers/cli/invoices.go @@ -1,57 +1,70 @@ -package handlers +package cli import ( - "html/template" - "log" + "fmt" "marmic/servicetrade-toolbox/internal/api" - "net/http" - "strings" + "marmic/servicetrade-toolbox/internal/ui" + "marmic/servicetrade-toolbox/internal/utils" ) -func InvoicesHandler(w http.ResponseWriter, r *http.Request) { - session, ok := r.Context().Value("session").(*api.Session) - if !ok { - http.Error(w, "Unauthorized", http.StatusUnauthorized) - return - } - - // Handle the search request - if r.Method == "GET" && r.URL.Query().Get("search") != "" { - handleInvoiceSearch(w, r, session) - return - } +func HandleInvoices(session *api.Session) { + for { + ui.ClearScreen() + fmt.Println("Invoices Menu:") + fmt.Println("1. Search Invoice") + fmt.Println("2. List Recent Invoices") + fmt.Println("3. Create Invoice") + fmt.Println("4. Back to Main Menu") - // Handle the initial page load - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/invoices.html")) - err := tmpl.Execute(w, nil) - if err != nil { - log.Printf("Error executing template: %v", err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) + choice, err := utils.GetUserChoice(4) + if err != nil { + ui.DisplayError("Invalid input:", err) + utils.PressEnterToContinue() + continue + } + switch choice { + case 1: + searchInvoice(session) + case 2: + listRecentInvoices(session) + case 3: + createInvoice(session) + case 4: + return + } } } -func handleInvoiceSearch(w http.ResponseWriter, r *http.Request, session *api.Session) { - searchTerm := strings.TrimSpace(r.URL.Query().Get("search")) - - if searchTerm == "" { - log.Println("Empty search term, returning empty response") - w.WriteHeader(http.StatusOK) - return - } +func searchInvoice(session *api.Session) { + ui.ClearScreen() + fmt.Println("Search Invoice:") + identifier := utils.PromptForInput("Enter Invoice Number or ID: ") - invoice, err := session.GetInvoice(searchTerm) + invoice, err := session.GetInvoice(identifier) if err != nil { - log.Printf("Error fetching invoice: %v", err) - w.WriteHeader(http.StatusInternalServerError) - tmpl := template.Must(template.ParseFiles("templates/partials/invoice_search_results.html")) - tmpl.ExecuteTemplate(w, "invoice_search_results", map[string]interface{}{"Error": err.Error()}) + fmt.Printf("Error fetching invoice: %v\n", err) + utils.PressEnterToContinue() return } - tmpl := template.Must(template.ParseFiles("templates/partials/invoice_search_results.html")) - err = tmpl.ExecuteTemplate(w, "invoice_search_results", invoice) - if err != nil { - log.Printf("Error executing template: %v", err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - } + fmt.Println("Invoice Details:") + fmt.Printf("Invoice Number: %v\n", invoice["invoiceNumber"]) + fmt.Printf("Total Price: $%v\n", invoice["totalPrice"]) + fmt.Printf("Status: %v\n", invoice["status"]) + + utils.PressEnterToContinue() +} + +func listRecentInvoices(session *api.Session) { + ui.ClearScreen() + fmt.Println("Listing recent invoices...") + // TODO: Implement recent invoices listing logic using the API + utils.PressEnterToContinue() +} + +func createInvoice(session *api.Session) { + ui.ClearScreen() + fmt.Println("Creating a new invoice...") + // TODO: Implement invoice creation logic using the API + utils.PressEnterToContinue() } diff --git a/internal/handlers/cli/jobs.go b/internal/handlers/cli/jobs.go index 6a08fc0..a598e5c 100644 --- a/internal/handlers/cli/jobs.go +++ b/internal/handlers/cli/jobs.go @@ -1,26 +1,116 @@ -package handlers +package cli import ( - "html/template" - "net/http" + "fmt" + "marmic/servicetrade-toolbox/internal/api" + "marmic/servicetrade-toolbox/internal/ui" + "marmic/servicetrade-toolbox/internal/utils" ) -func JobsHandler(w http.ResponseWriter, r *http.Request) { - jobs := []string{"Job 1", "Job 2", "Job 3"} // Replace with actual data fetching - - if r.Header.Get("HX-Request") == "true" { - // This is an HTMX request, return the jobs content wrapped in the content template - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/jobs.html")) - tmpl.ExecuteTemplate(w, "content", map[string]interface{}{ - "Title": "Jobs", - "Jobs": jobs, - }) - } else { - // This is a full page request - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/jobs.html")) - tmpl.Execute(w, map[string]interface{}{ - "Title": "Jobs", - "Jobs": jobs, - }) +func HandleJobs(session *api.Session) { + for { + ui.DisplayMenu([]string{ + "Search Job by ID", + "List Recent Jobs", + "Create New Job", + "Manage Job Attachments", + "View Deficiencies", + "Back to Main Menu", + }, "Jobs Menu") + + choice, err := utils.GetUserChoice(6) + if err != nil { + ui.DisplayError("Invalid input:", err) + utils.PressEnterToContinue() + continue + } + + switch choice { + case 1: + searchJobByID(session) + case 2: + listRecentJobs(session) + case 3: + createNewJob(session) + case 4: + manageJobAttachments(session) + case 5: + viewDeficiencyById(session) + case 6: + return + } + } +} + +func searchJobByID(session *api.Session) { + ui.ClearScreen() + ui.DisplayMessage("Search Job by ID:") + jobID := utils.PromptForInput("Enter Job ID: ") + + // TODO: Implement job search logic using the API + ui.DisplayMessage(fmt.Sprintf("Searching for job with ID: %s", jobID)) + utils.PressEnterToContinue() +} + +func listRecentJobs(session *api.Session) { + ui.ClearScreen() + ui.DisplayMessage("Listing recent jobs...") + // TODO: Implement recent jobs listing logic using the API + utils.PressEnterToContinue() +} + +func createNewJob(session *api.Session) { + ui.ClearScreen() + ui.DisplayMessage("Creating a new job...") + // TODO: Implement job creation logic using the API + utils.PressEnterToContinue() +} + +func manageJobAttachments(session *api.Session) { + ui.ClearScreen() + jobID := utils.PromptForInput("Enter Job ID: ") + + attachments, err := session.GetAttachmentsForJob(jobID) + if err != nil { + ui.DisplayError("Failed to retrieve attachments:", err) + utils.PressEnterToContinue() + return + } + + ui.DisplayMessage(fmt.Sprintf("Attachments for Job %s:", jobID)) + if dataMap, ok := attachments["data"].(map[string]interface{}); ok { + if attachmentsList, ok := dataMap["attachments"].([]interface{}); ok { + for i, attachment := range attachmentsList { + if att, ok := attachment.(map[string]interface{}); ok { + ui.DisplayMessage(fmt.Sprintf("%d. %s", i+1, att["fileName"])) + } + } + } + } + + // TODO: Implement attachment deletion logic + utils.PressEnterToContinue() +} + +func viewDeficiencyById(session *api.Session) { + ui.ClearScreen() + deficiencyID := utils.PromptForInput("Enter Deficiency ID: ") + + ui.DisplayMessage(fmt.Sprintf("Fetching information for Deficiency %s...", deficiencyID)) + + deficiencies, err := session.GetDeficiencyInfoForJob(deficiencyID) + if err != nil { + ui.DisplayError("Failed to retrieve deficiency information:", err) + utils.PressEnterToContinue() + return } + + for _, deficiency := range deficiencies { + ui.DisplayMessage(fmt.Sprintf("ID: %v", deficiency["id"])) + ui.DisplayMessage(fmt.Sprintf("Description: %v", deficiency["description"])) + ui.DisplayMessage(fmt.Sprintf("Status: %v", deficiency["status"])) + ui.DisplayMessage("---") + } + + utils.PressEnterToContinue() } diff --git a/internal/handlers/cli/locations.go b/internal/handlers/cli/locations.go index 000a488..4fec6db 100644 --- a/internal/handlers/cli/locations.go +++ b/internal/handlers/cli/locations.go @@ -1,11 +1,67 @@ -package handlers +package cli import ( - "html/template" - "net/http" + "fmt" + "marmic/servicetrade-toolbox/internal/api" + "marmic/servicetrade-toolbox/internal/ui" + "marmic/servicetrade-toolbox/internal/utils" ) -func LocationsHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/locations.html")) - tmpl.Execute(w, nil) +func HandleLocations(session *api.Session) { + for { + ui.ClearScreen() + fmt.Println("Locations Menu:") + fmt.Println("1. List Locations") + fmt.Println("2. Add Location") + fmt.Println("3. Edit Location") + fmt.Println("4. Delete Location") + fmt.Println("5. Back to Main Menu") + + choice, err := utils.GetUserChoice(5) + if err != nil { + ui.DisplayError("Invalid input:", err) + utils.PressEnterToContinue() + continue + } + switch choice { + case 1: + listLocations(session) + case 2: + addLocation(session) + case 3: + editLocation(session) + case 4: + deleteLocation(session) + case 5: + return + } + } +} + +func listLocations(session *api.Session) { + ui.ClearScreen() + fmt.Println("Listing locations...") + // TODO: Implement location listing logic using the API + utils.PressEnterToContinue() +} + +func addLocation(session *api.Session) { + ui.ClearScreen() + fmt.Println("Adding a new location...") + // TODO: Implement location creation logic using the API + utils.PressEnterToContinue() +} + +func editLocation(session *api.Session) { + ui.ClearScreen() + fmt.Println("Editing a location...") + // TODO: Implement location editing logic using the API + utils.PressEnterToContinue() +} + +func deleteLocation(session *api.Session) { + ui.ClearScreen() + fmt.Println("Deleting a location...") + // TODO: Implement location deletion logic using the API + utils.PressEnterToContinue() } diff --git a/internal/handlers/cli/login.go b/internal/handlers/cli/login.go index 3f31a4e..2cc253e 100644 --- a/internal/handlers/cli/login.go +++ b/internal/handlers/cli/login.go @@ -1,4 +1,4 @@ -package handlers +package cli import ( "html/template" diff --git a/internal/handlers/cli/notifications.go b/internal/handlers/cli/notifications.go index 1e86255..7b8ebc4 100644 --- a/internal/handlers/cli/notifications.go +++ b/internal/handlers/cli/notifications.go @@ -1,11 +1,57 @@ -package handlers +package cli import ( - "html/template" - "net/http" + "fmt" + "marmic/servicetrade-toolbox/internal/api" + "marmic/servicetrade-toolbox/internal/ui" + "marmic/servicetrade-toolbox/internal/utils" ) -func NotificationsHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/notifications.html")) - tmpl.Execute(w, nil) +func HandleNotifications(session *api.Session) { + for { + ui.ClearScreen() + fmt.Println("Notifications Menu:") + fmt.Println("1. View Notifications") + fmt.Println("2. Mark Notification as Read") + fmt.Println("3. Delete Notification") + fmt.Println("4. Back to Main Menu") + + choice, err := utils.GetUserChoice(4) + if err != nil { + ui.DisplayError("Invalid input:", err) + utils.PressEnterToContinue() + continue + } + switch choice { + case 1: + viewNotifications(session) + case 2: + markNotificationAsRead(session) + case 3: + deleteNotification(session) + case 4: + return + } + } +} + +func viewNotifications(session *api.Session) { + ui.ClearScreen() + fmt.Println("Viewing notifications...") + // TODO: Implement notification viewing logic using the API + utils.PressEnterToContinue() +} + +func markNotificationAsRead(session *api.Session) { + ui.ClearScreen() + fmt.Println("Marking notification as read...") + // TODO: Implement marking notification as read logic using the API + utils.PressEnterToContinue() +} + +func deleteNotification(session *api.Session) { + ui.ClearScreen() + fmt.Println("Deleting notification...") + // TODO: Implement notification deletion logic using the API + utils.PressEnterToContinue() } diff --git a/internal/handlers/cli/quotes.go b/internal/handlers/cli/quotes.go index 2bb895c..18a797f 100644 --- a/internal/handlers/cli/quotes.go +++ b/internal/handlers/cli/quotes.go @@ -1,11 +1,67 @@ -package handlers +package cli import ( - "html/template" - "net/http" + "fmt" + "marmic/servicetrade-toolbox/internal/api" + "marmic/servicetrade-toolbox/internal/ui" + "marmic/servicetrade-toolbox/internal/utils" ) -func QuotesHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/quotes.html")) - tmpl.Execute(w, nil) +func HandleQuotes(session *api.Session) { + for { + ui.ClearScreen() + fmt.Println("Quotes Menu:") + fmt.Println("1. List Quotes") + fmt.Println("2. Create Quote") + fmt.Println("3. Edit Quote") + fmt.Println("4. Delete Quote") + fmt.Println("5. Back to Main Menu") + + choice, err := utils.GetUserChoice(5) + if err != nil { + ui.DisplayError("Invalid input:", err) + utils.PressEnterToContinue() + continue + } + switch choice { + case 1: + listQuotes(session) + case 2: + createQuote(session) + case 3: + editQuote(session) + case 4: + deleteQuote(session) + case 5: + return + } + } +} + +func listQuotes(session *api.Session) { + ui.ClearScreen() + fmt.Println("Listing quotes...") + // TODO: Implement quote listing logic using the API + utils.PressEnterToContinue() +} + +func createQuote(session *api.Session) { + ui.ClearScreen() + fmt.Println("Creating a new quote...") + // TODO: Implement quote creation logic using the API + utils.PressEnterToContinue() +} + +func editQuote(session *api.Session) { + ui.ClearScreen() + fmt.Println("Editing a quote...") + // TODO: Implement quote editing logic using the API + utils.PressEnterToContinue() +} + +func deleteQuote(session *api.Session) { + ui.ClearScreen() + fmt.Println("Deleting a quote...") + // TODO: Implement quote deletion logic using the API + utils.PressEnterToContinue() } diff --git a/internal/handlers/cli/services.go b/internal/handlers/cli/services.go index 785835a..5d373d7 100644 --- a/internal/handlers/cli/services.go +++ b/internal/handlers/cli/services.go @@ -1,11 +1,67 @@ -package handlers +package cli import ( - "html/template" - "net/http" + "fmt" + "marmic/servicetrade-toolbox/internal/api" + "marmic/servicetrade-toolbox/internal/ui" + "marmic/servicetrade-toolbox/internal/utils" ) -func ServicesHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/services.html")) - tmpl.Execute(w, nil) +func HandleServices(session *api.Session) { + for { + ui.ClearScreen() + fmt.Println("Services Menu:") + fmt.Println("1. List Services") + fmt.Println("2. Add Service") + fmt.Println("3. Edit Service") + fmt.Println("4. Delete Service") + fmt.Println("5. Back to Main Menu") + + choice, err := utils.GetUserChoice(5) + if err != nil { + ui.DisplayError("Invalid input:", err) + utils.PressEnterToContinue() + continue + } + switch choice { + case 1: + listServices(session) + case 2: + addService(session) + case 3: + editService(session) + case 4: + deleteService(session) + case 5: + return + } + } +} + +func listServices(session *api.Session) { + ui.ClearScreen() + fmt.Println("Listing services...") + // TODO: Implement service listing logic using the API + utils.PressEnterToContinue() +} + +func addService(session *api.Session) { + ui.ClearScreen() + fmt.Println("Adding a new service...") + // TODO: Implement service creation logic using the API + utils.PressEnterToContinue() +} + +func editService(session *api.Session) { + ui.ClearScreen() + fmt.Println("Editing a service...") + // TODO: Implement service editing logic using the API + utils.PressEnterToContinue() +} + +func deleteService(session *api.Session) { + ui.ClearScreen() + fmt.Println("Deleting a service...") + // TODO: Implement service deletion logic using the API + utils.PressEnterToContinue() } diff --git a/internal/handlers/cli/tags.go b/internal/handlers/cli/tags.go index d4b37eb..f2a527a 100644 --- a/internal/handlers/cli/tags.go +++ b/internal/handlers/cli/tags.go @@ -1,11 +1,67 @@ -package handlers +package cli import ( - "html/template" - "net/http" + "fmt" + "marmic/servicetrade-toolbox/internal/api" + "marmic/servicetrade-toolbox/internal/ui" + "marmic/servicetrade-toolbox/internal/utils" ) -func TagsHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/tags.html")) - tmpl.Execute(w, nil) +func HandleTags(session *api.Session) { + for { + ui.ClearScreen() + fmt.Println("Tags Menu:") + fmt.Println("1. List Tags") + fmt.Println("2. Add Tag") + fmt.Println("3. Edit Tag") + fmt.Println("4. Delete Tag") + fmt.Println("5. Back to Main Menu") + + choice, err := utils.GetUserChoice(5) + if err != nil { + ui.DisplayError("Invalid input:", err) + utils.PressEnterToContinue() + continue + } + switch choice { + case 1: + listTags(session) + case 2: + addTag(session) + case 3: + editTag(session) + case 4: + deleteTag(session) + case 5: + return + } + } +} + +func listTags(session *api.Session) { + ui.ClearScreen() + fmt.Println("Listing tags...") + // TODO: Implement tag listing logic using the API + utils.PressEnterToContinue() +} + +func addTag(session *api.Session) { + ui.ClearScreen() + fmt.Println("Adding a new tag...") + // TODO: Implement tag creation logic using the API + utils.PressEnterToContinue() +} + +func editTag(session *api.Session) { + ui.ClearScreen() + fmt.Println("Editing a tag...") + // TODO: Implement tag editing logic using the API + utils.PressEnterToContinue() +} + +func deleteTag(session *api.Session) { + ui.ClearScreen() + fmt.Println("Deleting a tag...") + // TODO: Implement tag deletion logic using the API + utils.PressEnterToContinue() } diff --git a/internal/handlers/cli/users.go b/internal/handlers/cli/users.go index f471a8c..ba212c4 100644 --- a/internal/handlers/cli/users.go +++ b/internal/handlers/cli/users.go @@ -1,11 +1,67 @@ -package handlers +package cli import ( - "html/template" - "net/http" + "fmt" + "marmic/servicetrade-toolbox/internal/api" + "marmic/servicetrade-toolbox/internal/ui" + "marmic/servicetrade-toolbox/internal/utils" ) -func UsersHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/users.html")) - tmpl.Execute(w, nil) +func HandleUsers(session *api.Session) { + for { + ui.ClearScreen() + fmt.Println("Users Menu:") + fmt.Println("1. List Users") + fmt.Println("2. Add User") + fmt.Println("3. Edit User") + fmt.Println("4. Delete User") + fmt.Println("5. Back to Main Menu") + + choice, err := utils.GetUserChoice(5) + if err != nil { + ui.DisplayError("Invalid input:", err) + utils.PressEnterToContinue() + continue + } + switch choice { + case 1: + listUsers(session) + case 2: + addUser(session) + case 3: + editUser(session) + case 4: + deleteUser(session) + case 5: + return + } + } +} + +func listUsers(session *api.Session) { + ui.ClearScreen() + fmt.Println("Listing users...") + // TODO: Implement user listing logic using the API + utils.PressEnterToContinue() +} + +func addUser(session *api.Session) { + ui.ClearScreen() + fmt.Println("Adding a new user...") + // TODO: Implement user creation logic using the API + utils.PressEnterToContinue() +} + +func editUser(session *api.Session) { + ui.ClearScreen() + fmt.Println("Editing a user...") + // TODO: Implement user editing logic using the API + utils.PressEnterToContinue() +} + +func deleteUser(session *api.Session) { + ui.ClearScreen() + fmt.Println("Deleting a user...") + // TODO: Implement user deletion logic using the API + utils.PressEnterToContinue() } diff --git a/internal/handlers/web/admin.go b/internal/handlers/web/admin.go index 2ca106b..ff9dd20 100644 --- a/internal/handlers/web/admin.go +++ b/internal/handlers/web/admin.go @@ -1,4 +1,4 @@ -package handlers +package web import ( "html/template" @@ -6,19 +6,14 @@ import ( ) func AdminHandler(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/admin.html")) + data := map[string]interface{}{ + "Title": "Admin", + } + if r.Header.Get("HX-Request") == "true" { - // This is an HTMX request, return only the jobs partial - tmpl := template.Must(template.ParseFiles("templates/partials/jobs.html")) - jobs := r.Cookies() // Replace with actual data fetching - tmpl.Execute(w, jobs) + tmpl.ExecuteTemplate(w, "content", data) } else { - // This is a full page request - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/admin.html")) - jobs := []string{"Job 1", "Job 2", "Job 3"} // Replace with actual data fetching - - tmpl.Execute(w, map[string]interface{}{ - "Title": "Jobs", - "Jobs": jobs, - }) + tmpl.Execute(w, data) } } diff --git a/internal/handlers/web/assets.go b/internal/handlers/web/assets.go index 1f7d07f..12d9d35 100644 --- a/internal/handlers/web/assets.go +++ b/internal/handlers/web/assets.go @@ -1,4 +1,4 @@ -package handlers +package web import ( "html/template" @@ -6,6 +6,14 @@ import ( ) func AssetsHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/assets.html")) - tmpl.Execute(w, nil) + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/assets.html")) + data := map[string]interface{}{ + "Title": "Assets", + } + + if r.Header.Get("HX-Request") == "true" { + tmpl.ExecuteTemplate(w, "content", data) + } else { + tmpl.Execute(w, data) + } } diff --git a/internal/handlers/web/companies.go b/internal/handlers/web/companies.go index 744f039..d10e968 100644 --- a/internal/handlers/web/companies.go +++ b/internal/handlers/web/companies.go @@ -1,4 +1,4 @@ -package handlers +package web import ( "html/template" @@ -6,6 +6,14 @@ import ( ) func CompaniesHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/companies.html")) - tmpl.Execute(w, nil) + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/companies.html")) + data := map[string]interface{}{ + "Title": "Companies", + } + + if r.Header.Get("HX-Request") == "true" { + tmpl.ExecuteTemplate(w, "content", data) + } else { + tmpl.Execute(w, data) + } } diff --git a/internal/handlers/web/contacts.go b/internal/handlers/web/contacts.go index 46addef..468ba55 100644 --- a/internal/handlers/web/contacts.go +++ b/internal/handlers/web/contacts.go @@ -1,4 +1,4 @@ -package handlers +package web import ( "html/template" @@ -6,6 +6,14 @@ import ( ) func ContactsHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/contacts.html")) - tmpl.Execute(w, nil) + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/contacts.html")) + data := map[string]interface{}{ + "Title": "Contacts", + } + + if r.Header.Get("HX-Request") == "true" { + tmpl.ExecuteTemplate(w, "content", data) + } else { + tmpl.Execute(w, data) + } } diff --git a/internal/handlers/web/contracts.go b/internal/handlers/web/contracts.go index 20c3ccd..444ffd0 100644 --- a/internal/handlers/web/contracts.go +++ b/internal/handlers/web/contracts.go @@ -1,4 +1,4 @@ -package handlers +package web import ( "html/template" @@ -6,6 +6,14 @@ import ( ) func ContractsHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/contracts.html")) - tmpl.Execute(w, nil) + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/contracts.html")) + data := map[string]interface{}{ + "Title": "Contracts", + } + + if r.Header.Get("HX-Request") == "true" { + tmpl.ExecuteTemplate(w, "content", data) + } else { + tmpl.Execute(w, data) + } } diff --git a/internal/handlers/web/dashboard.go b/internal/handlers/web/dashboard.go index ecd687a..0bd11e3 100644 --- a/internal/handlers/web/dashboard.go +++ b/internal/handlers/web/dashboard.go @@ -1,4 +1,4 @@ -package handlers +package web import ( "html/template" diff --git a/internal/handlers/web/generic.go b/internal/handlers/web/generic.go index efe5640..ffa24e0 100644 --- a/internal/handlers/web/generic.go +++ b/internal/handlers/web/generic.go @@ -1,4 +1,4 @@ -package handlers +package web import ( "html/template" @@ -6,6 +6,14 @@ import ( ) func GenericHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/generic.html")) - tmpl.Execute(w, nil) + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/generic.html")) + data := map[string]interface{}{ + "Title": "Generic Tools", + } + + if r.Header.Get("HX-Request") == "true" { + tmpl.ExecuteTemplate(w, "content", data) + } else { + tmpl.Execute(w, data) + } } diff --git a/internal/handlers/web/invoices.go b/internal/handlers/web/invoices.go index 3e6e0bd..cd7ff9e 100644 --- a/internal/handlers/web/invoices.go +++ b/internal/handlers/web/invoices.go @@ -1,4 +1,4 @@ -package handlers +package web import ( "html/template" @@ -15,18 +15,20 @@ func InvoicesHandler(w http.ResponseWriter, r *http.Request) { return } - // Handle the search request if r.Method == "GET" && r.URL.Query().Get("search") != "" { handleInvoiceSearch(w, r, session) return } - // Handle the initial page load - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/invoices.html")) - err := tmpl.Execute(w, nil) - if err != nil { - log.Printf("Error executing template: %v", err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/invoices.html")) + data := map[string]interface{}{ + "Title": "Invoices", + } + + if r.Header.Get("HX-Request") == "true" { + tmpl.ExecuteTemplate(w, "content", data) + } else { + tmpl.Execute(w, data) } } diff --git a/internal/handlers/web/jobs.go b/internal/handlers/web/jobs.go index 6a08fc0..641dfe4 100644 --- a/internal/handlers/web/jobs.go +++ b/internal/handlers/web/jobs.go @@ -1,4 +1,4 @@ -package handlers +package web import ( "html/template" diff --git a/internal/handlers/web/locations.go b/internal/handlers/web/locations.go index 000a488..734f5b1 100644 --- a/internal/handlers/web/locations.go +++ b/internal/handlers/web/locations.go @@ -1,4 +1,4 @@ -package handlers +package web import ( "html/template" @@ -6,6 +6,14 @@ import ( ) func LocationsHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/locations.html")) - tmpl.Execute(w, nil) + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/locations.html")) + data := map[string]interface{}{ + "Title": "Locations", + } + + if r.Header.Get("HX-Request") == "true" { + tmpl.ExecuteTemplate(w, "content", data) + } else { + tmpl.Execute(w, data) + } } diff --git a/internal/handlers/web/login.go b/internal/handlers/web/login.go index 3f31a4e..43d2a0c 100644 --- a/internal/handlers/web/login.go +++ b/internal/handlers/web/login.go @@ -1,4 +1,4 @@ -package handlers +package web import ( "html/template" diff --git a/internal/handlers/web/notifications.go b/internal/handlers/web/notifications.go index 1e86255..1613470 100644 --- a/internal/handlers/web/notifications.go +++ b/internal/handlers/web/notifications.go @@ -1,4 +1,4 @@ -package handlers +package web import ( "html/template" @@ -6,6 +6,14 @@ import ( ) func NotificationsHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/notifications.html")) - tmpl.Execute(w, nil) + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/notifications.html")) + data := map[string]interface{}{ + "Title": "Notifications", + } + + if r.Header.Get("HX-Request") == "true" { + tmpl.ExecuteTemplate(w, "content", data) + } else { + tmpl.Execute(w, data) + } } diff --git a/internal/handlers/web/quotes.go b/internal/handlers/web/quotes.go index 2bb895c..90d8de5 100644 --- a/internal/handlers/web/quotes.go +++ b/internal/handlers/web/quotes.go @@ -1,4 +1,4 @@ -package handlers +package web import ( "html/template" @@ -6,6 +6,14 @@ import ( ) func QuotesHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/quotes.html")) - tmpl.Execute(w, nil) + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/quotes.html")) + data := map[string]interface{}{ + "Title": "Quotes", + } + + if r.Header.Get("HX-Request") == "true" { + tmpl.ExecuteTemplate(w, "content", data) + } else { + tmpl.Execute(w, data) + } } diff --git a/internal/handlers/web/services.go b/internal/handlers/web/services.go index 785835a..d3996eb 100644 --- a/internal/handlers/web/services.go +++ b/internal/handlers/web/services.go @@ -1,4 +1,4 @@ -package handlers +package web import ( "html/template" @@ -6,6 +6,14 @@ import ( ) func ServicesHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/services.html")) - tmpl.Execute(w, nil) + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/services.html")) + data := map[string]interface{}{ + "Title": "Services", + } + + if r.Header.Get("HX-Request") == "true" { + tmpl.ExecuteTemplate(w, "content", data) + } else { + tmpl.Execute(w, data) + } } diff --git a/internal/handlers/web/tags.go b/internal/handlers/web/tags.go index d4b37eb..576c8b6 100644 --- a/internal/handlers/web/tags.go +++ b/internal/handlers/web/tags.go @@ -1,4 +1,4 @@ -package handlers +package web import ( "html/template" @@ -6,6 +6,14 @@ import ( ) func TagsHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/tags.html")) - tmpl.Execute(w, nil) + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/tags.html")) + data := map[string]interface{}{ + "Title": "Tags", + } + + if r.Header.Get("HX-Request") == "true" { + tmpl.ExecuteTemplate(w, "content", data) + } else { + tmpl.Execute(w, data) + } } diff --git a/internal/handlers/web/users.go b/internal/handlers/web/users.go index f471a8c..f5be8f9 100644 --- a/internal/handlers/web/users.go +++ b/internal/handlers/web/users.go @@ -1,4 +1,4 @@ -package handlers +package web import ( "html/template" @@ -6,6 +6,14 @@ import ( ) func UsersHandler(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/users.html")) - tmpl.Execute(w, nil) + tmpl := template.Must(template.ParseFiles("templates/layout.html", "templates/partials/users.html")) + data := map[string]interface{}{ + "Title": "Users", + } + + if r.Header.Get("HX-Request") == "true" { + tmpl.ExecuteTemplate(w, "content", data) + } else { + tmpl.Execute(w, data) + } } diff --git a/internal/middleware/auth_middleware.go b/internal/middleware/auth_middleware.go new file mode 100644 index 0000000..461bc97 --- /dev/null +++ b/internal/middleware/auth_middleware.go @@ -0,0 +1,36 @@ +package middleware + +import ( + "context" + "marmic/servicetrade-toolbox/internal/api" + "net/http" +) + +var SessionStore = api.NewSessionStore() + +func AuthMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + cookie, err := r.Cookie("PHPSESSID") + if err != nil { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + sessionID := cookie.Value + session, exists := SessionStore.Get(sessionID) + if !exists { + session = api.NewSession() + session.Cookie = "PHPSESSID=" + sessionID + + if err := session.ValidateSession(); err != nil { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + SessionStore.Set(sessionID, session) + } + + ctx := context.WithValue(r.Context(), "session", session) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} diff --git a/internal/ui/ui.go b/internal/ui/ui.go new file mode 100644 index 0000000..8e6e03f --- /dev/null +++ b/internal/ui/ui.go @@ -0,0 +1,37 @@ +package ui + +import ( + "fmt" + + "github.com/inancgumus/screen" +) + +func ClearScreen() { + screen.Clear() + screen.MoveTopLeft() +} + +func DisplayStartScreen() { + ClearScreen() + fmt.Println("========================================") + fmt.Println(" Welcome to ServiceTrade CLI") + fmt.Println("========================================") + fmt.Println("Please log in with your ServiceTrade credentials to continue.") + fmt.Println() +} + +func DisplayMessage(message string) { + fmt.Println(message) +} + +func DisplayError(prefix string, err error) { + fmt.Printf("%s %v\n", prefix, err) +} + +func DisplayMenu(items []string, title string) { + ClearScreen() + fmt.Printf("\n%s:\n", title) + for i, item := range items { + fmt.Printf("%d. %s\n", i+1, item) + } +} diff --git a/internal/utils/ui.go b/internal/utils/ui.go deleted file mode 100644 index 16ca1e5..0000000 --- a/internal/utils/ui.go +++ /dev/null @@ -1,79 +0,0 @@ -package ui - -import ( - "bufio" - "fmt" - "os" - "strconv" - "strings" - - "github.com/inancgumus/screen" -) - -func ClearScreen() { - screen.Clear() - screen.MoveTopLeft() -} - -func DisplayStartScreen() { - ClearScreen() - fmt.Println("========================================") - fmt.Println(" Welcome to ServiceTrade CLI") - fmt.Println("========================================") - fmt.Println("Please log in with your ServiceTrade credentials to continue.") - fmt.Println() -} - -func PromptCredentials() (string, string, error) { - reader := bufio.NewReader(os.Stdin) - - fmt.Print("Enter your email: ") - email, _ := reader.ReadString('\n') - email = strings.TrimSpace(email) - - fmt.Print("Enter your password: ") - password, _ := reader.ReadString('\n') - password = strings.TrimSpace(password) - - return email, password, nil -} - -func DisplayMessage(message string) { - fmt.Println(message) -} - -func DisplayError(prefix string, err error) { - fmt.Printf("%s %v\n", prefix, err) -} - -func PressEnterToContinue() { - fmt.Println("Press Enter to continue...") - bufio.NewReader(os.Stdin).ReadBytes('\n') -} - -func DisplayMenu(items []string, title string) { - ClearScreen() - fmt.Printf("\n%s:\n", title) - for i, item := range items { - fmt.Printf("%d. %s\n", i+1, item) - } -} - -func GetUserChoice(max int) (int, error) { - reader := bufio.NewReader(os.Stdin) - fmt.Printf("\nEnter your choice (1-%d): ", max) - input, _ := reader.ReadString('\n') - input = strings.TrimSpace(input) - choice, err := strconv.Atoi(input) - if err != nil || choice < 1 || choice > max { - return 0, fmt.Errorf("invalid input") - } - return choice, nil -} - -func PromptForInput(prompt string) string { - reader := bufio.NewReader(os.Stdin) - fmt.Print(prompt) - input, _ := reader.ReadString('\n') - return strings.TrimSpace(input) -} diff --git a/internal/utils/utils.go b/internal/utils/utils.go index d82951a..d7c1c5f 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -6,9 +6,48 @@ import ( "fmt" "os" "path/filepath" + "strconv" "strings" ) +func PromptCredentials() (string, string, error) { + reader := bufio.NewReader(os.Stdin) + + fmt.Print("Enter your email: ") + email, _ := reader.ReadString('\n') + email = strings.TrimSpace(email) + + fmt.Print("Enter your password: ") + password, _ := reader.ReadString('\n') + password = strings.TrimSpace(password) + + return email, password, nil +} + +func GetUserChoice(max int) (int, error) { + reader := bufio.NewReader(os.Stdin) + fmt.Printf("\nEnter your choice (1-%d): ", max) + input, _ := reader.ReadString('\n') + input = strings.TrimSpace(input) + choice, err := strconv.Atoi(input) + if err != nil || choice < 1 || choice > max { + return 0, fmt.Errorf("invalid input") + } + return choice, nil +} + +func PromptForInput(prompt string) string { + reader := bufio.NewReader(os.Stdin) + fmt.Print(prompt) + input, _ := reader.ReadString('\n') + return strings.TrimSpace(input) +} + +func PressEnterToContinue() { + fmt.Println("Press Enter to continue...") + bufio.NewReader(os.Stdin).ReadBytes('\n') +} + // ReadCSV reads a CSV file and returns its contents as a slice of string slices func ReadCSV(filename string) ([][]string, error) { file, err := os.Open(filename)