feat: node search picker, ByNode grouping fix, PDF export

- node picker: Search/Path on Repository, SearchNodes binding,
  debounced search dropdown showing title + full path
- ByNode summary groups by nodeID with NodePath as label (not NodeTitle)
- PDF export for worklog reports with embedded DejaVuSans fonts
- ExportWorklogPDF binding + button on Journal screen
- Removed unused Section field from ReportFilter
- ListReport now calls BuildReportPaths so nodePath is available
- go.sum: +github.com/signintech/gopdf dependency
This commit is contained in:
2026-06-03 10:56:13 +08:00
parent 5732264fc5
commit d34100e2ed
17 changed files with 512 additions and 32 deletions
+40
View File
@@ -185,6 +185,46 @@ func (r *Repository) CountChildren(parentID string, types ...string) (int, error
return count, err
}
// Search finds active nodes whose title contains the query (case-insensitive).
func (r *Repository) Search(query string, limit int) ([]Node, error) {
q := `SELECT ` + nodeColumns + ` FROM nodes
WHERE deleted_at IS NULL AND title LIKE ? ORDER BY sort_order, title LIMIT ?`
rows, err := r.db.Query(q, "%"+query+"%", limit)
if err != nil {
return nil, err
}
defer rows.Close()
return scanNodes(rows)
}
// Path builds the full path from root to the given node by walking parent_id chain.
// Returns "title1 > title2 > ... > nodetitle". Returns empty string on error.
func (r *Repository) Path(nodeID string) string {
var segments []string
currentID := nodeID
seen := make(map[string]bool)
for i := 0; i < 10; i++ {
if currentID == "" || seen[currentID] {
break
}
seen[currentID] = true
var title string
var parent sql.NullString
err := r.db.QueryRow(
`SELECT title, parent_id FROM nodes WHERE id = ?`, currentID,
).Scan(&title, &parent)
if err != nil {
break
}
segments = append([]string{title}, segments...)
if !parent.Valid {
break
}
currentID = parent.String
}
return strings.Join(segments, " > ")
}
// ListByParent returns children as *Node pointers. parentID must not be empty.
func (r *Repository) ListByParent(parentID string) ([]*Node, error) {
rows, err := r.db.Query(