Redesign configurator UI with tabs and remove Excel export

- Add tab-based configurator (Base, Storage, PCI, Power, Accessories, Other)
- Base tab: single-select with autocomplete for MB, CPU, MEM
- Other tabs: multi-select with autocomplete and quantity input
- Table view with LOT, Description, Price, Quantity, Total columns
- Add configuration list page with create modal (opportunity number)
- Remove Excel export functionality and excelize dependency
- Increase component list limit from 100 to 5000
- Add web templates (base, index, configs, login, admin_pricing)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-01-26 15:57:15 +03:00
parent 44ccb01203
commit a93644131c
14 changed files with 2041 additions and 201 deletions

View File

@@ -6,9 +6,8 @@ import (
"fmt"
"time"
"github.com/mchus/quoteforge/internal/config"
"github.com/mchus/quoteforge/internal/models"
"github.com/xuri/excelize/v2"
"git.mchus.pro/mchus/quoteforge/internal/config"
"git.mchus.pro/mchus/quoteforge/internal/models"
)
type ExportService struct {
@@ -20,11 +19,11 @@ func NewExportService(cfg config.ExportConfig) *ExportService {
}
type ExportData struct {
Name string
Items []ExportItem
Total float64
Notes string
CreatedAt time.Time
Name string
Items []ExportItem
Total float64
Notes string
CreatedAt time.Time
}
type ExportItem struct {
@@ -70,86 +69,6 @@ func (s *ExportService) ToCSV(data *ExportData) ([]byte, error) {
return buf.Bytes(), w.Error()
}
func (s *ExportService) ToXLSX(data *ExportData) ([]byte, error) {
f := excelize.NewFile()
sheet := "Конфигурация"
f.SetSheetName("Sheet1", sheet)
// Styles
headerStyle, _ := f.NewStyle(&excelize.Style{
Font: &excelize.Font{Bold: true, Size: 12, Color: "#FFFFFF"},
Fill: excelize.Fill{Type: "pattern", Color: []string{"#4472C4"}, Pattern: 1},
Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
Border: []excelize.Border{
{Type: "left", Color: "#000000", Style: 1},
{Type: "top", Color: "#000000", Style: 1},
{Type: "bottom", Color: "#000000", Style: 1},
{Type: "right", Color: "#000000", Style: 1},
},
})
totalStyle, _ := f.NewStyle(&excelize.Style{
Font: &excelize.Font{Bold: true, Size: 12},
Fill: excelize.Fill{Type: "pattern", Color: []string{"#E2EFDA"}, Pattern: 1},
})
priceStyle, _ := f.NewStyle(&excelize.Style{
NumFmt: 4, // #,##0.00
})
// Title
f.SetCellValue(sheet, "A1", s.config.CompanyName)
f.SetCellValue(sheet, "A2", "Коммерческое предложение: "+data.Name)
f.SetCellValue(sheet, "A3", "Дата: "+data.CreatedAt.Format("02.01.2006"))
// Headers
headers := []string{"Артикул", "Описание", "Категория", "Кол-во", "Цена", "Сумма"}
for i, h := range headers {
cell := fmt.Sprintf("%c5", 'A'+i)
f.SetCellValue(sheet, cell, h)
f.SetCellStyle(sheet, cell, cell, headerStyle)
}
// Data rows
row := 6
for _, item := range data.Items {
f.SetCellValue(sheet, fmt.Sprintf("A%d", row), item.LotName)
f.SetCellValue(sheet, fmt.Sprintf("B%d", row), item.Description)
f.SetCellValue(sheet, fmt.Sprintf("C%d", row), item.Category)
f.SetCellValue(sheet, fmt.Sprintf("D%d", row), item.Quantity)
f.SetCellValue(sheet, fmt.Sprintf("E%d", row), item.UnitPrice)
f.SetCellValue(sheet, fmt.Sprintf("F%d", row), item.TotalPrice)
f.SetCellStyle(sheet, fmt.Sprintf("E%d", row), fmt.Sprintf("F%d", row), priceStyle)
row++
}
// Total row
f.SetCellValue(sheet, fmt.Sprintf("E%d", row), "ИТОГО:")
f.SetCellValue(sheet, fmt.Sprintf("F%d", row), data.Total)
f.SetCellStyle(sheet, fmt.Sprintf("E%d", row), fmt.Sprintf("F%d", row), totalStyle)
// Notes
if data.Notes != "" {
row += 2
f.SetCellValue(sheet, fmt.Sprintf("A%d", row), "Примечания: "+data.Notes)
}
// Column widths
f.SetColWidth(sheet, "A", "A", 25)
f.SetColWidth(sheet, "B", "B", 50)
f.SetColWidth(sheet, "C", "C", 15)
f.SetColWidth(sheet, "D", "D", 10)
f.SetColWidth(sheet, "E", "E", 15)
f.SetColWidth(sheet, "F", "F", 15)
var buf bytes.Buffer
if err := f.Write(&buf); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (s *ExportService) ConfigToExportData(config *models.Configuration) *ExportData {
items := make([]ExportItem, len(config.Items))
var total float64