diff --git a/apps/cli/main.go b/apps/cli/main.go deleted file mode 100644 index 839a9bc..0000000 --- a/apps/cli/main.go +++ /dev/null @@ -1,95 +0,0 @@ -package main - -import ( - "fmt" - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/handlers/cli" - "marmic/servicetrade-toolbox/internal/ui" - "marmic/servicetrade-toolbox/internal/utils" - "os" -) - -func main() { - ui.DisplayStartScreen() - - email, password, err := utils.PromptCredentials() - if err != nil { - ui.DisplayError("Error getting credentials:", err) - os.Exit(1) - } - - session := api.NewSession() - err = session.Login(email, password) - if err != nil { - ui.DisplayError("Authentication failed:", err) - os.Exit(1) - } - - fmt.Println("Login successful!") - - for { - 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 { - fmt.Println("Logout successful.") - } - fmt.Println("Exiting ServiceTrade CLI Toolbox. Goodbye!") - return - } - } -} diff --git a/apps/web/main.go b/apps/web/main.go index e4c89cf..8b5dd4f 100644 --- a/apps/web/main.go +++ b/apps/web/main.go @@ -58,6 +58,13 @@ func main() { protected.HandleFunc("/tags", web.TagsHandler).Methods("GET") protected.HandleFunc("/users", web.UsersHandler).Methods("GET") + // Document upload routes + protected.HandleFunc("/documents", web.DocumentsHandler).Methods("GET") + protected.HandleFunc("/process-csv", web.ProcessCSVHandler).Methods("POST") + protected.HandleFunc("/upload-documents", web.UploadDocumentsHandler).Methods("POST") + protected.HandleFunc("/document-field-add", web.DocumentFieldAddHandler).Methods("GET") + protected.HandleFunc("/document-field-remove", web.DocumentFieldRemoveHandler).Methods("GET") + log.Println("Server starting on :8080") log.Fatal(http.ListenAndServe(":8080", r)) } diff --git a/go.mod b/go.mod index 468e217..df7e9a9 100644 --- a/go.mod +++ b/go.mod @@ -2,13 +2,4 @@ module marmic/servicetrade-toolbox go 1.22.1 -require ( - github.com/gorilla/mux v1.8.1 - github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3 -) - -require ( - golang.org/x/crypto v0.28.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect -) +require github.com/gorilla/mux v1.8.1 diff --git a/go.sum b/go.sum index e7a6e59..7128337 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,2 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3 h1:fO9A67/izFYFYky7l1pDP5Dr0BTCRkaQJUG6Jm5ehsk= -github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3/go.mod h1:Ey4uAp+LvIl+s5jRbOHLcZpUDnkjLBROl15fZLwPlTM= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= diff --git a/internal/api/auth.go b/internal/api/auth.go index e4f92cc..9a51397 100644 --- a/internal/api/auth.go +++ b/internal/api/auth.go @@ -2,12 +2,11 @@ package api import ( "io" - "marmic/servicetrade-toolbox/internal/auth" "net/http" ) // AuthenticatedRequest wraps an http.Request with the session cookie -func AuthenticatedRequest(session *auth.Session, method, url string, body io.Reader) (*http.Request, error) { +func AuthenticatedRequest(session *Session, method, url string, body io.Reader) (*http.Request, error) { req, err := http.NewRequest(method, url, body) if err != nil { return nil, err @@ -17,6 +16,6 @@ func AuthenticatedRequest(session *auth.Session, method, url string, body io.Rea } // DoAuthenticatedRequest performs an authenticated request and returns the response -func DoAuthenticatedRequest(session *auth.Session, req *http.Request) (*http.Response, error) { +func DoAuthenticatedRequest(session *Session, req *http.Request) (*http.Response, error) { return session.Client.Do(req) } diff --git a/internal/api/deficiencies.go b/internal/api/deficiencies.go index f9839b7..9aa7cba 100644 --- a/internal/api/deficiencies.go +++ b/internal/api/deficiencies.go @@ -5,10 +5,9 @@ import ( "encoding/json" "fmt" "io" - "marmic/servicetrade-toolbox/internal/auth" ) -func GetDeficiencyById(session *auth.Session, deficiencyId string) (map[string]interface{}, error) { +func GetDeficiencyById(session *Session, deficiencyId string) (map[string]interface{}, error) { url := fmt.Sprintf("%s/deficiency/%s", BaseURL, deficiencyId) req, err := AuthenticatedRequest(session, "GET", url, nil) if err != nil { diff --git a/internal/api/jobs.go b/internal/api/jobs.go index c0cc6dd..2c9627d 100644 --- a/internal/api/jobs.go +++ b/internal/api/jobs.go @@ -5,153 +5,120 @@ import ( "fmt" "io" "log" + "net/http" "net/url" + "strconv" + "time" ) -type JobsResponse struct { - Data struct { - Jobs []Job `json:"jobs"` - } `json:"data"` +// SearchJobsParams defines parameters for searching jobs +type SearchJobsParams struct { + Query string + Status string + StartDate time.Time + EndDate time.Time + Page int + Limit int + IncludeArchived bool } -type Job struct { - ID int64 `json:"id"` - Name string `json:"name"` - CustomName *string `json:"customName"` - Type string `json:"type"` - JobTypeWeight int `json:"jobTypeWeight"` - Status string `json:"status"` - Visibility []string `json:"visibility"` - Number int `json:"number"` - RefNumber string `json:"refNumber"` - Description *string `json:"description"` - ScheduledDate *int64 `json:"scheduledDate"` - CompletedOn *int64 `json:"completedOn"` - ServiceLine string `json:"serviceLine"` - EstimatedPrice *float64 `json:"estimatedPrice"` - Vendor Vendor `json:"vendor"` - Customer Customer `json:"customer"` - Location Location `json:"location"` - Owner Owner `json:"owner"` - Tags []Tag `json:"tags"` - Appointments []Appointment `json:"appointments"` - CurrentAppointment Appointment `json:"currentAppointment"` - AssignedOffice Location `json:"assignedOffice"` - Offices []Location `json:"offices"` - Terms *Term `json:"terms"` - Contract *Contract `json:"contract"` - PrimaryContact *PrimaryContact `json:"primaryContact"` -} +// SearchJobs searches for jobs based on the provided parameters +func (s *Session) SearchJobs(params SearchJobsParams) ([]map[string]interface{}, error) { + queryValues := url.Values{} -type Vendor struct { - ID int64 `json:"id"` - URI string `json:"uri"` - Name string `json:"name"` - Status string `json:"status"` -} + if params.Query != "" { + queryValues.Add("q", params.Query) + } -type Customer struct { - ID int64 `json:"id"` - URI string `json:"uri"` - Name string `json:"name"` - Status string `json:"status"` -} + if params.Status != "" { + queryValues.Add("status", params.Status) + } -type Location struct { - ID int64 `json:"id"` - URI string `json:"uri"` - Name string `json:"name"` - RefNumber string `json:"refNumber"` - Lat float64 `json:"lat"` - Lon float64 `json:"lon"` - Address Address `json:"address"` -} + if !params.StartDate.IsZero() { + queryValues.Add("start", params.StartDate.Format("2006-01-02")) + } -type Address struct { - Street string `json:"street"` - City string `json:"city"` - State string `json:"state"` - PostalCode string `json:"postalCode"` -} + if !params.EndDate.IsZero() { + queryValues.Add("end", params.EndDate.Format("2006-01-02")) + } -func (a Address) String() string { - return fmt.Sprintf("%s, %s, %s %s", a.Street, a.City, a.State, a.PostalCode) -} + if params.Page > 0 { + queryValues.Add("page", strconv.Itoa(params.Page)) + } else { + queryValues.Add("page", "1") + } -type Owner struct { - ID int64 `json:"id"` - URI string `json:"uri"` - Name string `json:"name"` - Status string `json:"status"` - Email string `json:"email"` -} + if params.Limit > 0 { + queryValues.Add("limit", strconv.Itoa(params.Limit)) + } else { + queryValues.Add("limit", "50") + } -type Tag struct { - ID int64 `json:"id"` - URI string `json:"uri"` - Name string `json:"name"` -} + if params.IncludeArchived { + queryValues.Add("include", "archived") + } -type Appointment struct { - ID int64 `json:"id"` - URI string `json:"uri"` - Name string `json:"name"` - Status string `json:"status"` - WindowStart *int64 `json:"windowStart"` - WindowEnd *int64 `json:"windowEnd"` - Techs []Tech `json:"techs"` - Released bool `json:"released"` -} + endpoint := fmt.Sprintf("/job?%s", queryValues.Encode()) + resp, err := s.DoRequest(http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() -type Tech struct { - ID int64 `json:"id"` - Name string `json:"name"` - Email string `json:"email"` -} + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %v", err) + } -type Term struct { - ID int64 `json:"id"` - Name string `json:"name"` -} + if resp.StatusCode != 200 { + return nil, fmt.Errorf("API returned error: %s - %s", resp.Status, string(body)) + } -type Contract struct { - ID int64 `json:"id"` - Name string `json:"name"` -} + var result struct { + Data struct { + Jobs []map[string]interface{} `json:"records"` + } `json:"data"` + } -type PrimaryContact struct { - ID int64 `json:"id"` - URI string `json:"uri"` - Email string `json:"email"` -} + if err := json.Unmarshal(body, &result); err != nil { + return nil, fmt.Errorf("error unmarshalling response: %v", err) + } -func (s *Session) SearchJobs(filters url.Values) ([]Job, error) { - endpoint := "/job?" - query := filters.Encode() - url := endpoint + query + log.Printf("Found %d jobs", len(result.Data.Jobs)) + log.Printf("Parsed Data: %+v", result.Data) + return result.Data.Jobs, nil +} - resp, err := s.DoRequest("GET", url, nil) +// GetJobDetails retrieves detailed information about a specific job +func (s *Session) GetJobDetails(jobID string) (map[string]interface{}, error) { + endpoint := fmt.Sprintf("/job/%s", jobID) + resp, err := s.DoRequest(http.MethodGet, endpoint, nil) if err != nil { return nil, err } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) - log.Printf("Raw API Response: %s", string(body)) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %v", err) + } if resp.StatusCode != 200 { - return nil, fmt.Errorf("failed to search jobs: %s, response: %s", resp.Status, string(body)) + return nil, fmt.Errorf("API returned error: %s - %s", resp.Status, string(body)) + } + + var result struct { + Data map[string]interface{} `json:"data"` } - var result JobsResponse if err := json.Unmarshal(body, &result); err != nil { return nil, fmt.Errorf("error unmarshalling response: %v", err) } - log.Printf("Parsed Data: %+v", result.Data) - return result.Data.Jobs, nil + return result.Data, nil } +// GetAttachmentsForJob retrieves attachments for a specific job 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 { @@ -171,21 +138,7 @@ func (s *Session) GetAttachmentsForJob(jobID string) (map[string]interface{}, er 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 -} - +// GetDeficiencyInfoForJob retrieves deficiency information for a specific job func (s *Session) GetDeficiencyInfoForJob(jobID string) ([]map[string]interface{}, error) { resp, err := s.DoRequest("GET", fmt.Sprintf("/deficiency/%s", jobID), nil) if err != nil { diff --git a/internal/auth/auth.go b/internal/auth/auth.go deleted file mode 100644 index 082586d..0000000 --- a/internal/auth/auth.go +++ /dev/null @@ -1,82 +0,0 @@ -package auth - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "strings" -) - -type Session struct { - Client *http.Client - Cookie string -} - -func NewSession() *Session { - return &Session{Client: &http.Client{}} -} - -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)) - 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) - } - defer resp.Body.Close() - - body, _ := io.ReadAll(resp.Body) - - if resp.StatusCode != 200 { - return fmt.Errorf("failed to authenticate: %s, response: %s", resp.Status, string(body)) - } - - for _, cookie := range resp.Cookies() { - if strings.Contains(cookie.Name, "PHPSESSID") { - s.Cookie = cookie.String() - return nil - } - } - - return fmt.Errorf("failed to retrieve session cookie; authentication may have failed") -} - -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) - if err != nil { - return fmt.Errorf("failed to send DELETE request to end session: %v", err) - } - defer resp.Body.Close() - - if resp.StatusCode != 200 && resp.StatusCode != 204 { - body, _ := io.ReadAll(resp.Body) - return fmt.Errorf("failed to end session: %s, response: %s", resp.Status, string(body)) - } - - s.Cookie = "" - return nil -} diff --git a/internal/handlers/cli/admin.go b/internal/handlers/cli/admin.go deleted file mode 100644 index 379e598..0000000 --- a/internal/handlers/cli/admin.go +++ /dev/null @@ -1,48 +0,0 @@ -package cli - -import ( - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/ui" - "marmic/servicetrade-toolbox/internal/utils" -) - -func HandleAdmin(session *api.Session) { - for { - ui.ClearScreen() - ui.DisplayMenu([]string{ - "User Management", - "System Settings", - "Back to Main Menu", - }, "Admin Menu") - - 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 deleted file mode 100644 index 3ee5ce0..0000000 --- a/internal/handlers/cli/assets.go +++ /dev/null @@ -1,67 +0,0 @@ -package cli - -import ( - "fmt" - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/ui" - "marmic/servicetrade-toolbox/internal/utils" -) - -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 deleted file mode 100644 index c37e441..0000000 --- a/internal/handlers/cli/companies.go +++ /dev/null @@ -1,67 +0,0 @@ -package cli - -import ( - "fmt" - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/ui" - "marmic/servicetrade-toolbox/internal/utils" -) - -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 deleted file mode 100644 index ced6f97..0000000 --- a/internal/handlers/cli/contacts.go +++ /dev/null @@ -1,67 +0,0 @@ -package cli - -import ( - "fmt" - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/ui" - "marmic/servicetrade-toolbox/internal/utils" -) - -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 deleted file mode 100644 index 05c8e40..0000000 --- a/internal/handlers/cli/contracts.go +++ /dev/null @@ -1,67 +0,0 @@ -package cli - -import ( - "fmt" - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/ui" - "marmic/servicetrade-toolbox/internal/utils" -) - -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/generic.go b/internal/handlers/cli/generic.go deleted file mode 100644 index ef1bab0..0000000 --- a/internal/handlers/cli/generic.go +++ /dev/null @@ -1,57 +0,0 @@ -package cli - -import ( - "fmt" - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/ui" - "marmic/servicetrade-toolbox/internal/utils" -) - -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 deleted file mode 100644 index 752ed07..0000000 --- a/internal/handlers/cli/invoices.go +++ /dev/null @@ -1,70 +0,0 @@ -package cli - -import ( - "fmt" - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/ui" - "marmic/servicetrade-toolbox/internal/utils" -) - -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") - - 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 searchInvoice(session *api.Session) { - ui.ClearScreen() - fmt.Println("Search Invoice:") - identifier := utils.PromptForInput("Enter Invoice Number or ID: ") - - invoice, err := session.GetInvoice(identifier) - if err != nil { - fmt.Printf("Error fetching invoice: %v\n", err) - utils.PressEnterToContinue() - return - } - - 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 deleted file mode 100644 index a598e5c..0000000 --- a/internal/handlers/cli/jobs.go +++ /dev/null @@ -1,116 +0,0 @@ -package cli - -import ( - "fmt" - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/ui" - "marmic/servicetrade-toolbox/internal/utils" -) - -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 deleted file mode 100644 index 4fec6db..0000000 --- a/internal/handlers/cli/locations.go +++ /dev/null @@ -1,67 +0,0 @@ -package cli - -import ( - "fmt" - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/ui" - "marmic/servicetrade-toolbox/internal/utils" -) - -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 deleted file mode 100644 index 2cc253e..0000000 --- a/internal/handlers/cli/login.go +++ /dev/null @@ -1,104 +0,0 @@ -package cli - -import ( - "html/template" - "log" - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/middleware" - "net/http" - "strings" -) - -func LoginHandler(w http.ResponseWriter, r *http.Request) { - if r.Method == "GET" { - tmpl := template.Must(template.ParseFiles("templates/login.html")) - tmpl.Execute(w, nil) - return - } - - if r.Method == "POST" { - email := r.FormValue("email") - password := r.FormValue("password") - - session := api.NewSession() - err := session.Login(email, password) - if err != nil { - if r.Header.Get("HX-Request") == "true" { - w.Write([]byte("
Login failed: " + err.Error() + "
")) - } else { - http.Error(w, "Login failed", http.StatusUnauthorized) - } - return - } - - cookieParts := strings.Split(session.Cookie, ";") - sessionID := strings.TrimPrefix(cookieParts[0], "PHPSESSID=") - - middleware.SessionStore.Set(sessionID, session) - - http.SetCookie(w, &http.Cookie{ - Name: "PHPSESSID", - Value: sessionID, - Path: "/", - HttpOnly: true, - Secure: r.TLS != nil, - SameSite: http.SameSiteLaxMode, - }) - - if r.Header.Get("HX-Request") == "true" { - w.Header().Set("HX-Redirect", "/") - w.WriteHeader(http.StatusOK) - w.Write([]byte("Login successful")) - } else { - http.Redirect(w, r, "/", http.StatusSeeOther) - } - } -} - -func LogoutHandler(w http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie("PHPSESSID") - if err != nil { - log.Printf("No session cookie found: %v", err) - redirectToLogin(w, r) - return - } - - sessionID := cookie.Value - session, exists := middleware.SessionStore.Get(sessionID) - if !exists { - log.Println("No session found in store") - redirectToLogin(w, r) - return - } - - err = session.Logout() - if err != nil { - log.Printf("Logout failed: %v", err) - http.Error(w, "Logout failed", http.StatusInternalServerError) - return - } - - middleware.SessionStore.Delete(sessionID) - - http.SetCookie(w, &http.Cookie{ - Name: "PHPSESSID", - Value: "", - Path: "/", - MaxAge: -1, - HttpOnly: true, - Secure: r.TLS != nil, - SameSite: http.SameSiteLaxMode, - }) - - log.Println("Logout successful, redirecting to login page") - redirectToLogin(w, r) -} - -func redirectToLogin(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("HX-Request") != "" { - w.Header().Set("HX-Redirect", "/login") - w.WriteHeader(http.StatusOK) - } else { - http.Redirect(w, r, "/login", http.StatusSeeOther) - } -} diff --git a/internal/handlers/cli/notifications.go b/internal/handlers/cli/notifications.go deleted file mode 100644 index 7b8ebc4..0000000 --- a/internal/handlers/cli/notifications.go +++ /dev/null @@ -1,57 +0,0 @@ -package cli - -import ( - "fmt" - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/ui" - "marmic/servicetrade-toolbox/internal/utils" -) - -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 deleted file mode 100644 index 18a797f..0000000 --- a/internal/handlers/cli/quotes.go +++ /dev/null @@ -1,67 +0,0 @@ -package cli - -import ( - "fmt" - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/ui" - "marmic/servicetrade-toolbox/internal/utils" -) - -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 deleted file mode 100644 index 5d373d7..0000000 --- a/internal/handlers/cli/services.go +++ /dev/null @@ -1,67 +0,0 @@ -package cli - -import ( - "fmt" - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/ui" - "marmic/servicetrade-toolbox/internal/utils" -) - -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 deleted file mode 100644 index f2a527a..0000000 --- a/internal/handlers/cli/tags.go +++ /dev/null @@ -1,67 +0,0 @@ -package cli - -import ( - "fmt" - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/ui" - "marmic/servicetrade-toolbox/internal/utils" -) - -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 deleted file mode 100644 index ba212c4..0000000 --- a/internal/handlers/cli/users.go +++ /dev/null @@ -1,67 +0,0 @@ -package cli - -import ( - "fmt" - "marmic/servicetrade-toolbox/internal/api" - "marmic/servicetrade-toolbox/internal/ui" - "marmic/servicetrade-toolbox/internal/utils" -) - -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/dashboard.go b/internal/handlers/web/dashboard.go index 84fd432..7968bb5 100644 --- a/internal/handlers/web/dashboard.go +++ b/internal/handlers/web/dashboard.go @@ -7,18 +7,25 @@ import ( ) func DashboardHandler(w http.ResponseWriter, r *http.Request) { - var err error tmpl := root.WebTemplates - data := struct{}{} // Empty struct as data + data := map[string]interface{}{ + "Title": "Dashboard", + } if r.Header.Get("HX-Request") == "true" { - err = tmpl.ExecuteTemplate(w, "content", data) + // For HTMX requests, execute the dashboard template directly + if err := tmpl.ExecuteTemplate(w, "dashboard.html", data); err != nil { + log.Printf("Template execution error: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } } 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 + // For full page requests, we'll use the layout template + // which will include the content template + if err := tmpl.ExecuteTemplate(w, "layout.html", data); err != nil { + log.Printf("Template execution error: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } } } diff --git a/internal/handlers/web/jobs.go b/internal/handlers/web/jobs.go index d00996c..81cd65a 100644 --- a/internal/handlers/web/jobs.go +++ b/internal/handlers/web/jobs.go @@ -7,6 +7,7 @@ import ( "marmic/servicetrade-toolbox/internal/api" "net/http" "net/url" + "strconv" "time" ) @@ -60,6 +61,7 @@ func JobsHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, "Not Found", http.StatusNotFound) } } + func convertDatesToUnix(params url.Values) url.Values { cleanedParams := url.Values{} @@ -93,11 +95,47 @@ func convertDatesToUnix(params url.Values) url.Values { return cleanedParams } -func handleJobSearch(r *http.Request, session *api.Session) ([]api.Job, error) { + +func handleJobSearch(r *http.Request, session *api.Session) ([]map[string]interface{}, error) { queryParams := r.URL.Query() cleanedParams := convertDatesToUnix(queryParams) - jobs, err := session.SearchJobs(cleanedParams) + // Convert url.Values to SearchJobsParams + params := api.SearchJobsParams{ + Query: cleanedParams.Get("q"), + Status: cleanedParams.Get("status"), + } + + // Convert page and limit if present + if pageStr := cleanedParams.Get("page"); pageStr != "" { + if page, err := strconv.Atoi(pageStr); err == nil { + params.Page = page + } + } + + if limitStr := cleanedParams.Get("limit"); limitStr != "" { + if limit, err := strconv.Atoi(limitStr); err == nil { + params.Limit = limit + } + } + + // Handle start/end dates + if startStr := cleanedParams.Get("start"); startStr != "" { + if start, err := time.Parse("2006-01-02", startStr); err == nil { + params.StartDate = start + } + } + + if endStr := cleanedParams.Get("end"); endStr != "" { + if end, err := time.Parse("2006-01-02", endStr); err == nil { + params.EndDate = end + } + } + + // Handle include archived + params.IncludeArchived = cleanedParams.Get("include") == "archived" + + jobs, err := session.SearchJobs(params) if err != nil { return nil, fmt.Errorf("error fetching jobs: %w", err) } diff --git a/internal/ui/ui.go b/internal/ui/ui.go deleted file mode 100644 index 8e6e03f..0000000 --- a/internal/ui/ui.go +++ /dev/null @@ -1,37 +0,0 @@ -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/utils.go b/internal/utils/utils.go index d7c1c5f..bd12faa 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -1,53 +1,11 @@ package utils import ( - "bufio" "encoding/csv" - "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) @@ -74,51 +32,36 @@ func WriteCSV(filename string, data [][]string) error { return writer.WriteAll(data) } -// ChooseFile allows the user to select a file from the current directory -func ChooseFile(extension string) (string, error) { - currentDir, err := os.Getwd() - if err != nil { - return "", err - } - - files, err := filepath.Glob(filepath.Join(currentDir, "*"+extension)) - if err != nil { - return "", err - } - - if len(files) == 0 { - return "", fmt.Errorf("no %s files found in the current directory", extension) - } - - fmt.Printf("Available %s files:\n", extension) - for i, file := range files { - fmt.Printf("%d. %s\n", i+1, filepath.Base(file)) - } +// ReadCSVFromBytes reads CSV data from a byte slice +func ReadCSVFromBytes(data []byte) ([][]string, error) { + reader := csv.NewReader(strings.NewReader(string(data))) + return reader.ReadAll() +} - var choice int - fmt.Print("Enter the number of the file you want to select: ") - _, err = fmt.Scanf("%d", &choice) - if err != nil || choice < 1 || choice > len(files) { - return "", fmt.Errorf("invalid selection") +// CSVToMap converts CSV data to a map of maps for easier access +// The first row is assumed to be headers +func CSVToMap(data [][]string) ([]map[string]string, error) { + if len(data) < 2 { + return nil, nil // No data or just headers } - return files[choice-1], nil -} - -// PromptYesNo prompts the user for a yes/no response -func PromptYesNo(prompt string) bool { - reader := bufio.NewReader(os.Stdin) - for { - fmt.Printf("%s (y/n): ", prompt) - response, _ := reader.ReadString('\n') - response = strings.ToLower(strings.TrimSpace(response)) + headers := data[0] + result := make([]map[string]string, 0, len(data)-1) - if response == "y" || response == "yes" { - return true - } else if response == "n" || response == "no" { - return false + for i := 1; i < len(data); i++ { + row := data[i] + if len(row) == 0 { + continue // Skip empty rows } - fmt.Println("Please answer with 'y' or 'n'.") + rowMap := make(map[string]string) + for j, value := range row { + if j < len(headers) { + rowMap[headers[j]] = value + } + } + result = append(result, rowMap) } + + return result, nil } diff --git a/static/css/styles.css b/static/css/styles.css index 8e8a411..aaad47d 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -472,21 +472,115 @@ html { /* Progress bar styling */ .progress { - background-color: var(--progress-bg); - height: 1rem; - border-radius: 0.25rem; + height: 20px; + margin-bottom: 10px; overflow: hidden; - margin: 0.5rem 0; + background-color: var(--progress-bg, #e9ecef); + border-radius: 4px; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); } .progress-bar { + float: left; + width: 0; height: 100%; - background-color: var(--progress-fill); - transition: width 0.3s ease; + font-size: 12px; + line-height: 20px; + color: #fff; + text-align: center; + background-color: var(--progress-bar-bg, #0d6efd); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + transition: width 0.6s ease; } -#upload-progress { - margin-top: 1rem; +/* Upload results styling */ +.upload-results { + margin-top: 20px; + border: 1px solid var(--border-color); + border-radius: 4px; + padding: 15px; + background-color: var(--card-bg); +} + +.upload-results h4 { + margin-top: 0; + margin-bottom: 15px; + color: var(--heading-color); +} + +.job-result { + margin-bottom: 15px; +} + +.job-result h5 { + margin-top: 0; + margin-bottom: 10px; + color: var(--subheading-color); +} + +.job-result ul { + list-style-type: none; + padding-left: 0; + margin-top: 0; + margin-bottom: 0; +} + +.job-result li { + padding: 5px 0; +} + +.job-result li.success { + color: var(--success-color, #198754); +} + +.job-result li.error { + color: var(--error-color, #dc3545); +} + +/* Loading indicator */ +.loading-indicator { + display: inline-block; + width: 20px; + height: 20px; + border: 3px solid rgba(0, 0, 0, 0.3); + border-radius: 50%; + border-top-color: var(--progress-bar-bg, #0d6efd); + animation: spin 1s ease-in-out infinite; + vertical-align: middle; + margin-left: 10px; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* Job list styling */ +.job-list { + list-style-type: none; + padding-left: 0; + max-height: 200px; + overflow-y: auto; + border: 1px solid var(--border-color); + border-radius: 4px; + padding: 10px; + background-color: var(--input-bg); +} + +.job-list li { + padding: 5px 10px; + cursor: pointer; + transition: background-color 0.2s; +} + +.job-list li:hover { + background-color: var(--hover-bg); +} + +.job-list li.selected { + background-color: var(--selected-bg); + color: var(--selected-text); } /* Style for error and not-found message display */ @@ -535,41 +629,6 @@ html { content: "🔍"; } -/* Loading indicator */ -@keyframes spin { - 0% { - transform: rotate(0deg); - } - - 100% { - transform: rotate(360deg); - } -} - -.htmx-indicator { - display: none; -} - -.htmx-request .htmx-indicator { - display: flex; - align-items: center; -} - -.htmx-request.htmx-indicator { - display: flex; - align-items: center; -} - -.loading-indicator { - width: 1.5rem; - height: 1.5rem; - border: 0.25rem solid var(--progress-bg); - border-top: 0.25rem solid var(--progress-fill); - border-radius: 50%; - animation: spin 1s linear infinite; - margin-right: 0.5rem; -} - /* Fade effect for existing content */ .htmx-request .fade-me-out { opacity: 0.5; diff --git a/templates/layout.html b/templates/layout.html index c0b3acb..b2694fd 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -42,8 +42,16 @@ -
{{template "content" .}}
+
+ {{if .BodyContent}} + {{.BodyContent}} + {{else}} + {{template "content" .}} + {{end}} +
+ + \ No newline at end of file diff --git a/templates/partials/csv_upload.html b/templates/partials/csv_upload.html index 0f09612..69283c8 100644 --- a/templates/partials/csv_upload.html +++ b/templates/partials/csv_upload.html @@ -1,11 +1,14 @@ {{define "csv_upload"}}
- +
- + - +