package ui import ( "fmt" "strings" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" "git.warky.dev/wdevs/relspecgo/pkg/models" ) // showRelationshipList displays all relationships for a table func (se *SchemaEditor) showRelationshipList(schemaIndex, tableIndex int) { table := se.GetTable(schemaIndex, tableIndex) if table == nil { return } flex := tview.NewFlex().SetDirection(tview.FlexRow) // Title title := tview.NewTextView(). SetText(fmt.Sprintf("[::b]Relationships for Table: %s", table.Name)). SetDynamicColors(true). SetTextAlign(tview.AlignCenter) // Create relationships table relTable := tview.NewTable().SetBorders(true).SetSelectable(true, false).SetFixed(1, 0) // Add header row headers := []string{"Name", "Type", "From Columns", "To Table", "To Columns", "Description"} headerWidths := []int{20, 15, 20, 20, 20} for i, header := range headers { padding := "" if i < len(headerWidths) { padding = strings.Repeat(" ", headerWidths[i]-len(header)) } cell := tview.NewTableCell(header + padding). SetTextColor(tcell.ColorYellow). SetSelectable(false). SetAlign(tview.AlignLeft) relTable.SetCell(0, i, cell) } // Get relationship names relNames := se.GetRelationshipNames(schemaIndex, tableIndex) for row, relName := range relNames { rel := table.Relationships[relName] // Name nameStr := fmt.Sprintf("%-20s", rel.Name) nameCell := tview.NewTableCell(nameStr).SetSelectable(true) relTable.SetCell(row+1, 0, nameCell) // Type typeStr := fmt.Sprintf("%-15s", string(rel.Type)) typeCell := tview.NewTableCell(typeStr).SetSelectable(true) relTable.SetCell(row+1, 1, typeCell) // From Columns fromColsStr := strings.Join(rel.FromColumns, ", ") fromColsStr = fmt.Sprintf("%-20s", fromColsStr) fromColsCell := tview.NewTableCell(fromColsStr).SetSelectable(true) relTable.SetCell(row+1, 2, fromColsCell) // To Table toTableStr := rel.ToTable if rel.ToSchema != "" && rel.ToSchema != table.Schema { toTableStr = rel.ToSchema + "." + rel.ToTable } toTableStr = fmt.Sprintf("%-20s", toTableStr) toTableCell := tview.NewTableCell(toTableStr).SetSelectable(true) relTable.SetCell(row+1, 3, toTableCell) // To Columns toColsStr := strings.Join(rel.ToColumns, ", ") toColsStr = fmt.Sprintf("%-20s", toColsStr) toColsCell := tview.NewTableCell(toColsStr).SetSelectable(true) relTable.SetCell(row+1, 4, toColsCell) // Description descCell := tview.NewTableCell(rel.Description).SetSelectable(true) relTable.SetCell(row+1, 5, descCell) } relTable.SetTitle(" Relationships ").SetBorder(true).SetTitleAlign(tview.AlignLeft) // Action buttons btnFlex := tview.NewFlex() btnNew := tview.NewButton("New Relationship [n]").SetSelectedFunc(func() { se.showNewRelationshipDialog(schemaIndex, tableIndex) }) btnEdit := tview.NewButton("Edit [e]").SetSelectedFunc(func() { row, _ := relTable.GetSelection() if row > 0 && row <= len(relNames) { relName := relNames[row-1] se.showEditRelationshipDialog(schemaIndex, tableIndex, relName) } }) btnDelete := tview.NewButton("Delete [d]").SetSelectedFunc(func() { row, _ := relTable.GetSelection() if row > 0 && row <= len(relNames) { relName := relNames[row-1] se.showDeleteRelationshipConfirm(schemaIndex, tableIndex, relName) } }) btnBack := tview.NewButton("Back [b]").SetSelectedFunc(func() { se.pages.RemovePage("relationships") se.pages.SwitchToPage("table-editor") }) // Set up button navigation btnNew.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyBacktab { se.app.SetFocus(relTable) return nil } if event.Key() == tcell.KeyTab { se.app.SetFocus(btnEdit) return nil } return event }) btnEdit.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyBacktab { se.app.SetFocus(btnNew) return nil } if event.Key() == tcell.KeyTab { se.app.SetFocus(btnDelete) return nil } return event }) btnDelete.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyBacktab { se.app.SetFocus(btnEdit) return nil } if event.Key() == tcell.KeyTab { se.app.SetFocus(btnBack) return nil } return event }) btnBack.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyBacktab { se.app.SetFocus(btnDelete) return nil } if event.Key() == tcell.KeyTab { se.app.SetFocus(relTable) return nil } return event }) btnFlex.AddItem(btnNew, 0, 1, true). AddItem(btnEdit, 0, 1, false). AddItem(btnDelete, 0, 1, false). AddItem(btnBack, 0, 1, false) relTable.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyEscape { se.pages.RemovePage("relationships") se.pages.SwitchToPage("table-editor") return nil } if event.Key() == tcell.KeyTab { se.app.SetFocus(btnNew) return nil } if event.Key() == tcell.KeyEnter { row, _ := relTable.GetSelection() if row > 0 && row <= len(relNames) { relName := relNames[row-1] se.showEditRelationshipDialog(schemaIndex, tableIndex, relName) } return nil } if event.Rune() == 'n' { se.showNewRelationshipDialog(schemaIndex, tableIndex) return nil } if event.Rune() == 'e' { row, _ := relTable.GetSelection() if row > 0 && row <= len(relNames) { relName := relNames[row-1] se.showEditRelationshipDialog(schemaIndex, tableIndex, relName) } return nil } if event.Rune() == 'd' { row, _ := relTable.GetSelection() if row > 0 && row <= len(relNames) { relName := relNames[row-1] se.showDeleteRelationshipConfirm(schemaIndex, tableIndex, relName) } return nil } if event.Rune() == 'b' { se.pages.RemovePage("relationships") se.pages.SwitchToPage("table-editor") return nil } return event }) flex.AddItem(title, 1, 0, false). AddItem(relTable, 0, 1, true). AddItem(btnFlex, 1, 0, false) se.pages.AddPage("relationships", flex, true, true) } // showNewRelationshipDialog shows dialog to create a new relationship func (se *SchemaEditor) showNewRelationshipDialog(schemaIndex, tableIndex int) { table := se.GetTable(schemaIndex, tableIndex) if table == nil { return } form := tview.NewForm() // Collect all tables for dropdown var allTables []string var tableMap []struct{ schemaIdx, tableIdx int } for si, schema := range se.db.Schemas { for ti, t := range schema.Tables { tableName := t.Name if schema.Name != table.Schema { tableName = schema.Name + "." + t.Name } allTables = append(allTables, tableName) tableMap = append(tableMap, struct{ schemaIdx, tableIdx int }{si, ti}) } } relName := "" relType := models.OneToMany fromColumns := "" toColumns := "" description := "" selectedTableIdx := 0 form.AddInputField("Name", "", 40, nil, func(value string) { relName = value }) form.AddDropDown("Type", []string{ string(models.OneToOne), string(models.OneToMany), string(models.ManyToMany), }, 1, func(option string, optionIndex int) { relType = models.RelationType(option) }) form.AddInputField("From Columns (comma-separated)", "", 40, nil, func(value string) { fromColumns = value }) form.AddDropDown("To Table", allTables, 0, func(option string, optionIndex int) { selectedTableIdx = optionIndex }) form.AddInputField("To Columns (comma-separated)", "", 40, nil, func(value string) { toColumns = value }) form.AddInputField("Description", "", 60, nil, func(value string) { description = value }) form.AddButton("Save", func() { if relName == "" { return } // Parse columns fromCols := strings.Split(fromColumns, ",") for i := range fromCols { fromCols[i] = strings.TrimSpace(fromCols[i]) } toCols := strings.Split(toColumns, ",") for i := range toCols { toCols[i] = strings.TrimSpace(toCols[i]) } // Get target table targetSchema := se.db.Schemas[tableMap[selectedTableIdx].schemaIdx] targetTable := targetSchema.Tables[tableMap[selectedTableIdx].tableIdx] rel := models.InitRelationship(relName, relType) rel.FromTable = table.Name rel.FromSchema = table.Schema rel.FromColumns = fromCols rel.ToTable = targetTable.Name rel.ToSchema = targetTable.Schema rel.ToColumns = toCols rel.Description = description se.CreateRelationship(schemaIndex, tableIndex, rel) se.pages.RemovePage("new-relationship") se.pages.RemovePage("relationships") se.showRelationshipList(schemaIndex, tableIndex) }) form.AddButton("Back", func() { se.pages.RemovePage("new-relationship") }) form.SetBorder(true).SetTitle(" New Relationship ").SetTitleAlign(tview.AlignLeft) form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyEscape { se.pages.RemovePage("new-relationship") return nil } return event }) se.pages.AddPage("new-relationship", form, true, true) } // showEditRelationshipDialog shows dialog to edit a relationship func (se *SchemaEditor) showEditRelationshipDialog(schemaIndex, tableIndex int, relName string) { table := se.GetTable(schemaIndex, tableIndex) if table == nil { return } rel := se.GetRelationship(schemaIndex, tableIndex, relName) if rel == nil { return } form := tview.NewForm() // Collect all tables for dropdown var allTables []string var tableMap []struct{ schemaIdx, tableIdx int } selectedTableIdx := 0 for si, schema := range se.db.Schemas { for ti, t := range schema.Tables { tableName := t.Name if schema.Name != table.Schema { tableName = schema.Name + "." + t.Name } allTables = append(allTables, tableName) tableMap = append(tableMap, struct{ schemaIdx, tableIdx int }{si, ti}) // Check if this is the current target table if t.Name == rel.ToTable && schema.Name == rel.ToSchema { selectedTableIdx = len(allTables) - 1 } } } newName := rel.Name relType := rel.Type fromColumns := strings.Join(rel.FromColumns, ", ") toColumns := strings.Join(rel.ToColumns, ", ") description := rel.Description form.AddInputField("Name", rel.Name, 40, nil, func(value string) { newName = value }) // Find initial type index typeIdx := 1 // OneToMany default typeOptions := []string{ string(models.OneToOne), string(models.OneToMany), string(models.ManyToMany), } for i, opt := range typeOptions { if opt == string(rel.Type) { typeIdx = i break } } form.AddDropDown("Type", typeOptions, typeIdx, func(option string, optionIndex int) { relType = models.RelationType(option) }) form.AddInputField("From Columns (comma-separated)", fromColumns, 40, nil, func(value string) { fromColumns = value }) form.AddDropDown("To Table", allTables, selectedTableIdx, func(option string, optionIndex int) { selectedTableIdx = optionIndex }) form.AddInputField("To Columns (comma-separated)", toColumns, 40, nil, func(value string) { toColumns = value }) form.AddInputField("Description", rel.Description, 60, nil, func(value string) { description = value }) form.AddButton("Save", func() { if newName == "" { return } // Parse columns fromCols := strings.Split(fromColumns, ",") for i := range fromCols { fromCols[i] = strings.TrimSpace(fromCols[i]) } toCols := strings.Split(toColumns, ",") for i := range toCols { toCols[i] = strings.TrimSpace(toCols[i]) } // Get target table targetSchema := se.db.Schemas[tableMap[selectedTableIdx].schemaIdx] targetTable := targetSchema.Tables[tableMap[selectedTableIdx].tableIdx] updatedRel := models.InitRelationship(newName, relType) updatedRel.FromTable = table.Name updatedRel.FromSchema = table.Schema updatedRel.FromColumns = fromCols updatedRel.ToTable = targetTable.Name updatedRel.ToSchema = targetTable.Schema updatedRel.ToColumns = toCols updatedRel.Description = description updatedRel.GUID = rel.GUID se.UpdateRelationship(schemaIndex, tableIndex, relName, updatedRel) se.pages.RemovePage("edit-relationship") se.pages.RemovePage("relationships") se.showRelationshipList(schemaIndex, tableIndex) }) form.AddButton("Back", func() { se.pages.RemovePage("edit-relationship") }) form.SetBorder(true).SetTitle(" Edit Relationship ").SetTitleAlign(tview.AlignLeft) form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyEscape { se.pages.RemovePage("edit-relationship") return nil } return event }) se.pages.AddPage("edit-relationship", form, true, true) } // showDeleteRelationshipConfirm shows confirmation dialog for deleting a relationship func (se *SchemaEditor) showDeleteRelationshipConfirm(schemaIndex, tableIndex int, relName string) { modal := tview.NewModal(). SetText(fmt.Sprintf("Delete relationship '%s'? This action cannot be undone.", relName)). AddButtons([]string{"Cancel", "Delete"}). SetDoneFunc(func(buttonIndex int, buttonLabel string) { if buttonLabel == "Delete" { se.DeleteRelationship(schemaIndex, tableIndex, relName) se.pages.RemovePage("delete-relationship-confirm") se.pages.RemovePage("relationships") se.showRelationshipList(schemaIndex, tableIndex) } else { se.pages.RemovePage("delete-relationship-confirm") } }) modal.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyEscape { se.pages.RemovePage("delete-relationship-confirm") return nil } return event }) se.pages.AddAndSwitchToPage("delete-relationship-confirm", modal, true) }