Web UI - Infor CloudSuite-style split-panel layout (items.html rewrite): - Replace modal-based item detail with inline split-panel workspace - Horizontal mode: item list on left, tabbed detail panel on right - Vertical mode: detail panel on top, item list below - Detail tabs: Main, Properties, Revisions, BOM, Where Used - Ctrl+F opens in-page filter overlay with fuzzy search - Column config gear icon with per-layout-mode persistence - Search scope toggle pills (All / Part Number / Description) - Selected row highlight with accent border - Responsive breakpoint forces vertical below 900px - Create/Edit/Delete remain as modal dialogs Web UI - Projects page: - New projects.html template with full CRUD - Project table: Code, Name, Description, Item count, Created, Actions - Create/Edit/Delete modals - Click project code navigates to items filtered by project - 3-tab navigation in base.html: Items, Projects, Schemas Fuzzy search: - Add sahilm/fuzzy dependency for ranked text matching - New internal/api/search.go with SearchableItems fuzzy.Source - GET /api/items/search endpoint with field scope and type/project filters - Frontend routes to fuzzy endpoint when search input is non-empty Odoo ERP integration scaffold: - Migration 008: integrations and sync_log tables - internal/odoo/ package: types, client stubs, sync stubs - internal/db/integrations.go: IntegrationRepository - internal/config/config.go: OdooConfig struct - 6 API endpoints for config CRUD, sync log, test, push, pull - All sync operations return stub responses Documentation: - docs/REPOSITORY_STATUS.md: comprehensive repository state report with architecture overview, API surface, feature stubs, and potential issues analysis
73 lines
1.6 KiB
Go
73 lines
1.6 KiB
Go
package api
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/sahilm/fuzzy"
|
|
)
|
|
|
|
// SearchableItems implements the fuzzy.Source interface for item search.
|
|
type SearchableItems struct {
|
|
items []ItemResponse
|
|
fields []string
|
|
}
|
|
|
|
// String returns the searchable text for item at index i.
|
|
func (s SearchableItems) String(i int) string {
|
|
item := s.items[i]
|
|
var parts []string
|
|
|
|
for _, field := range s.fields {
|
|
switch field {
|
|
case "part_number":
|
|
parts = append(parts, item.PartNumber)
|
|
case "description":
|
|
parts = append(parts, item.Description)
|
|
case "item_type":
|
|
parts = append(parts, item.ItemType)
|
|
}
|
|
}
|
|
|
|
if len(parts) == 0 {
|
|
// Default: search all text fields
|
|
parts = append(parts, item.PartNumber, item.Description, item.ItemType)
|
|
}
|
|
|
|
return strings.Join(parts, " ")
|
|
}
|
|
|
|
// Len returns the number of items.
|
|
func (s SearchableItems) Len() int {
|
|
return len(s.items)
|
|
}
|
|
|
|
// FuzzyResult wraps an ItemResponse with match score.
|
|
type FuzzyResult struct {
|
|
ItemResponse
|
|
Score int `json:"score"`
|
|
}
|
|
|
|
// FuzzySearch runs fuzzy matching on items and returns ranked results.
|
|
func FuzzySearch(pattern string, items []ItemResponse, fields []string, limit int) []FuzzyResult {
|
|
if pattern == "" || len(items) == 0 {
|
|
return nil
|
|
}
|
|
|
|
source := SearchableItems{items: items, fields: fields}
|
|
matches := fuzzy.FindFrom(pattern, source)
|
|
|
|
results := make([]FuzzyResult, 0, len(matches))
|
|
for _, m := range matches {
|
|
results = append(results, FuzzyResult{
|
|
ItemResponse: items[m.Index],
|
|
Score: m.Score,
|
|
})
|
|
}
|
|
|
|
if limit > 0 && len(results) > limit {
|
|
results = results[:limit]
|
|
}
|
|
|
|
return results
|
|
}
|