summaryrefslogtreecommitdiff
path: root/internal/tui/clients_projects_box.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/tui/clients_projects_box.go')
-rw-r--r--internal/tui/clients_projects_box.go247
1 files changed, 0 insertions, 247 deletions
diff --git a/internal/tui/clients_projects_box.go b/internal/tui/clients_projects_box.go
deleted file mode 100644
index c52e964..0000000
--- a/internal/tui/clients_projects_box.go
+++ /dev/null
@@ -1,247 +0,0 @@
-package tui
-
-import (
- "fmt"
-
- "punchcard/internal/queries"
-
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss/v2"
-)
-
-// NewClientsProjectsModel creates a new clients/projects model
-func NewClientsProjectsModel() ClientsProjectsModel {
- return ClientsProjectsModel{
- selectedIndex: 0,
- selectedIsClient: true,
- }
-}
-
-// Update handles messages for the clients/projects box
-func (m ClientsProjectsModel) Update(msg tea.Msg) (ClientsProjectsModel, tea.Cmd) {
- return m, nil
-}
-
-// View renders the clients/projects box
-func (m ClientsProjectsModel) View(width, height int, isSelected bool) string {
- var content string
-
- if len(m.clients) == 0 {
- content = "No clients found\n\nUse 'punch add client' to\nadd your first client."
- } else {
- content = m.renderClientsAndProjects()
- }
-
- // Apply box styling
- style := unselectedBoxStyle
- if isSelected {
- style = selectedBoxStyle
- }
-
- title := "👥 Clients & Projects"
-
- return style.Width(width).Height(height).Render(
- fmt.Sprintf("%s\n\n%s", title, content),
- )
-}
-
-// renderClientsAndProjects renders the clients and their projects
-func (m ClientsProjectsModel) renderClientsAndProjects() string {
- var content string
-
- // Group projects by client
- projectsByClient := make(map[int64][]queries.ListAllProjectsRow)
- for _, project := range m.projects {
- projectsByClient[project.ClientID] = append(projectsByClient[project.ClientID], project)
- }
-
- // Track the absolute row index for selection highlighting
- absoluteRowIndex := 0
-
- for i, client := range m.clients {
- if i > 0 {
- content += "\n"
- }
-
- // Client name with rate if available
- clientLine := fmt.Sprintf("• %s", client.Name)
- if client.BillableRate.Valid {
- rateInDollars := float64(client.BillableRate.Int64) / 100.0
- clientLine += fmt.Sprintf(" ($%.2f/hr)", rateInDollars)
- }
-
- // Highlight if this client is selected
- clientStyle := lipgloss.NewStyle().Bold(true)
- if m.selectedIsClient && m.selectedIndex == i {
- clientStyle = clientStyle.Background(lipgloss.Color("62")).Foreground(lipgloss.Color("230"))
- }
- content += clientStyle.Render(clientLine) + "\n"
- absoluteRowIndex++
-
- // Projects for this client
- clientProjects := projectsByClient[client.ID]
- if len(clientProjects) == 0 {
- content += " └── (no projects)\n"
- } else {
- for j, project := range clientProjects {
- prefix := "├──"
- if j == len(clientProjects)-1 {
- prefix = "└──"
- }
-
- projectLine := fmt.Sprintf(" %s %s", prefix, project.Name)
- if project.BillableRate.Valid {
- rateInDollars := float64(project.BillableRate.Int64) / 100.0
- projectLine += fmt.Sprintf(" ($%.2f/hr)", rateInDollars)
- }
-
- // Highlight if this project is selected
- // We need to check against the absolute project index in m.projects
- projectStyle := lipgloss.NewStyle()
- if !m.selectedIsClient {
- // Find this project's index in the m.projects slice
- for k, p := range m.projects {
- if p.ID == project.ID && m.selectedIndex == k {
- projectStyle = projectStyle.Background(lipgloss.Color("62")).Foreground(lipgloss.Color("230"))
- break
- }
- }
- }
- content += projectStyle.Render(projectLine) + "\n"
- }
- }
- }
-
- return content
-}
-
-// UpdateData updates the clients and projects data
-func (m ClientsProjectsModel) UpdateData(clients []queries.Client, projects []queries.ListAllProjectsRow) ClientsProjectsModel {
- m.clients = clients
- m.projects = projects
- // Reset selection if we have new data
- if len(clients) > 0 {
- m.selectedIndex = 0
- m.selectedIsClient = true
- }
- return m
-}
-
-// NextSelection moves to the next selectable row
-func (m ClientsProjectsModel) NextSelection() ClientsProjectsModel {
- totalRows := m.getTotalSelectableRows()
- if totalRows == 0 {
- return m
- }
-
- currentIndex := m.getCurrentRowIndex()
- if currentIndex < totalRows-1 {
- m.setRowIndex(currentIndex + 1)
- }
- return m
-}
-
-// PrevSelection moves to the previous selectable row
-func (m ClientsProjectsModel) PrevSelection() ClientsProjectsModel {
- totalRows := m.getTotalSelectableRows()
- if totalRows == 0 {
- return m
- }
-
- currentIndex := m.getCurrentRowIndex()
- if currentIndex > 0 {
- m.setRowIndex(currentIndex - 1)
- }
- return m
-}
-
-// getDisplayOrder returns items in the order they are displayed (tree structure)
-func (m ClientsProjectsModel) getDisplayOrder() []ProjectsDisplayItem {
- var items []ProjectsDisplayItem
-
- // Group projects by client
- projectsByClient := make(map[int64][]queries.ListAllProjectsRow)
- projectIndexByID := make(map[int64]int)
- for i, project := range m.projects {
- projectsByClient[project.ClientID] = append(projectsByClient[project.ClientID], project)
- projectIndexByID[project.ID] = i
- }
-
- // Build display order: client followed by its projects
- for i, client := range m.clients {
- // Add client
- items = append(items, ProjectsDisplayItem{
- IsClient: true,
- ClientIndex: i,
- Client: &client,
- })
-
- // Add projects for this client
- clientProjects := projectsByClient[client.ID]
- for _, project := range clientProjects {
- projectCopy := project // Copy to avoid reference issues
- items = append(items, ProjectsDisplayItem{
- IsClient: false,
- ClientIndex: i,
- ProjectIndex: projectIndexByID[project.ID],
- Project: &projectCopy,
- })
- }
- }
-
- return items
-}
-
-// getTotalSelectableRows counts total items in display order
-func (m ClientsProjectsModel) getTotalSelectableRows() int {
- return len(m.getDisplayOrder())
-}
-
-// getCurrentRowIndex gets the current absolute row index in display order
-func (m ClientsProjectsModel) getCurrentRowIndex() int {
- displayOrder := m.getDisplayOrder()
-
- for i, item := range displayOrder {
- if item.IsClient && m.selectedIsClient && item.ClientIndex == m.selectedIndex {
- return i
- }
- if !item.IsClient && !m.selectedIsClient && item.ProjectIndex == m.selectedIndex {
- return i
- }
- }
-
- return 0 // Default to first item if not found
-}
-
-// setRowIndex sets the selection to the given absolute row index in display order
-func (m *ClientsProjectsModel) setRowIndex(index int) {
- displayOrder := m.getDisplayOrder()
- if index < 0 || index >= len(displayOrder) {
- return
- }
-
- item := displayOrder[index]
- if item.IsClient {
- m.selectedIndex = item.ClientIndex
- m.selectedIsClient = true
- } else {
- m.selectedIndex = item.ProjectIndex
- m.selectedIsClient = false
- }
-}
-
-// GetSelectedClient returns the selected client if one is selected
-func (m ClientsProjectsModel) GetSelectedClient() *queries.Client {
- if m.selectedIsClient && m.selectedIndex < len(m.clients) {
- return &m.clients[m.selectedIndex]
- }
- return nil
-}
-
-// GetSelectedProject returns the selected project if one is selected
-func (m ClientsProjectsModel) GetSelectedProject() *queries.ListAllProjectsRow {
- if !m.selectedIsClient && m.selectedIndex < len(m.projects) {
- return &m.projects[m.selectedIndex]
- }
- return nil
-} \ No newline at end of file