github.com/shoshinnikita/budget-manager@v0.7.1-0.20220131195411-8c46ff1c6778/internal/web/pages/spend_type.go (about)

     1  package pages
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/ShoshinNikita/budget-manager/internal/db"
     8  )
     9  
    10  type SpendType struct {
    11  	db.SpendType
    12  
    13  	// FullName is a composite name that contains names of parent Spend Types
    14  	FullName string
    15  	// parentSpendTypeIDs is a set of ids of parent Spend Types
    16  	parentSpendTypeIDs map[uint]struct{}
    17  }
    18  
    19  // getSpendTypesWithFullNames returns sorted Spend Types with full name
    20  func getSpendTypesWithFullNames(spendTypes []db.SpendType) []SpendType {
    21  	spendTypesMap := make(map[uint]db.SpendType, len(spendTypes))
    22  	for _, t := range spendTypes {
    23  		spendTypesMap[t.ID] = t
    24  	}
    25  
    26  	res := make([]SpendType, 0, len(spendTypes))
    27  	for _, t := range spendTypes {
    28  		fullName, parentIDs := getSpendTypeFullName(spendTypesMap, t.ID)
    29  		res = append(res, SpendType{
    30  			SpendType: t,
    31  			//
    32  			FullName:           fullName,
    33  			parentSpendTypeIDs: parentIDs,
    34  		})
    35  	}
    36  
    37  	sort.Slice(res, func(i, j int) bool {
    38  		return res[i].FullName < res[j].FullName
    39  	})
    40  
    41  	return res
    42  }
    43  
    44  func getSpendTypeFullName(spendTypes map[uint]db.SpendType, typeID uint) (name string, parentIDs map[uint]struct{}) {
    45  	const maxDepth = 15
    46  
    47  	parentIDs = make(map[uint]struct{})
    48  
    49  	var getFullName func(currentDepth int, currentType db.SpendType) string
    50  	getFullName = func(currentDepth int, currentType db.SpendType) string {
    51  		if currentDepth >= maxDepth {
    52  			return "..."
    53  		}
    54  		if currentType.ParentID == 0 {
    55  			return currentType.Name
    56  		}
    57  
    58  		nextType := spendTypes[currentType.ParentID]
    59  		if nextType.Name == "" {
    60  			return currentType.Name
    61  		}
    62  
    63  		parentIDs[currentType.ParentID] = struct{}{}
    64  
    65  		// Use thin spaces 'ā€‰' to separate names
    66  		return fmt.Sprintf("%sā€‰/ā€‰%s", getFullName(currentDepth+1, nextType), currentType.Name)
    67  	}
    68  
    69  	if spendType, ok := spendTypes[typeID]; ok {
    70  		return getFullName(0, spendType), parentIDs
    71  	}
    72  	return "", nil
    73  }
    74  
    75  // populateMonthlyPaymentsWithFullSpendTypeNames replaces Spend Type names to full ones
    76  func populateMonthlyPaymentsWithFullSpendTypeNames(spendTypes []SpendType, monthlyPayments []db.MonthlyPayment) {
    77  	fullNames := make(map[uint]string, len(spendTypes))
    78  	for _, t := range spendTypes {
    79  		fullNames[t.ID] = t.FullName
    80  	}
    81  
    82  	for i := range monthlyPayments {
    83  		if monthlyPayments[i].Type != nil {
    84  			if fullName, ok := fullNames[monthlyPayments[i].Type.ID]; ok {
    85  				monthlyPayments[i].Type.Name = fullName
    86  			}
    87  		}
    88  	}
    89  }
    90  
    91  // populateSpendsWithFullSpendTypeNames replaces Spend Type names to full ones
    92  func populateSpendsWithFullSpendTypeNames(spendTypes []SpendType, spends []db.Spend) {
    93  	fullNames := make(map[uint]string, len(spendTypes))
    94  	for _, t := range spendTypes {
    95  		fullNames[t.ID] = t.FullName
    96  	}
    97  
    98  	for i := range spends {
    99  		if spends[i].Type != nil {
   100  			if fullName, ok := fullNames[spends[i].Type.ID]; ok {
   101  				spends[i].Type.Name = fullName
   102  			}
   103  		}
   104  	}
   105  }