diff --git a/internal/config/config.go b/internal/config/config.go index 10441e02..2d1ffa0a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -115,6 +115,15 @@ type AllocationTarget struct { Accounts []string `json:"accounts" yaml:"accounts"` } +type CreditCard struct { + Account string `json:"account" yaml:"account"` + CreditLimit uint64 `json:"credit_limit" yaml:"credit_limit"` + StatementEndDay uint64 `json:"statement_end_day" yaml:"statement_end_day"` + DueDay uint64 `json:"due_day" yaml:"due_day"` + Network string `json:"network" yaml:"network"` + Number string `json:"number" yaml:"number"` +} + type Config struct { JournalPath string `json:"journal_path" yaml:"journal_path"` DBPath string `json:"db_path" yaml:"db_path"` @@ -142,6 +151,8 @@ type Config struct { Goals Goals `json:"goals" yaml:"goals"` UserAccounts []UserAccount `json:"user_accounts" yaml:"user_accounts"` + + CreditCards []CreditCard `json:"credit_cards" yaml:"credit_cards"` } var config Config @@ -164,6 +175,7 @@ var defaultConfig = Config{ Accounts: []Account{}, Goals: Goals{Retirement: []RetirementGoal{}, Savings: []SavingsGoal{}}, UserAccounts: []UserAccount{}, + CreditCards: []CreditCard{}, } var itemsUniquePropertiesMeta = jsonschema.MustCompileString("itemsUniqueProperties.json", `{ diff --git a/internal/config/schema.json b/internal/config/schema.json index b37cea06..744a6f94 100644 --- a/internal/config/schema.json +++ b/internal/config/schema.json @@ -341,7 +341,7 @@ "properties": { "name": { "type": "string", - "description": "name of the commodity" + "description": "Name of the commodity" }, "type": { "type": "string", @@ -395,7 +395,7 @@ "properties": { "name": { "type": "string", - "description": "name of the template", + "description": "Name of the template", "minLength": 1 }, "content": { @@ -422,7 +422,7 @@ "properties": { "name": { "type": "string", - "description": "name of the account", + "description": "Name of the account", "minLength": 1 }, "icon": { @@ -434,6 +434,66 @@ "required": ["name"], "additionalProperties": false } + }, + "credit_cards": { + "type": "array", + "itemsUniqueProperties": ["account"], + "default": [ + { + "account": "Liabilities:CreditCard:Chase", + "credit_limit": 100000, + "statement_end_day": 28, + "due_day": 15 + } + ], + "items": { + "type": "object", + "ui:header": "account", + "properties": { + "account": { + "type": "string", + "description": "Name of the credit card account" + }, + "credit_limit": { + "type": "number", + "description": "Credit limit of the card", + "minimum": 1 + }, + "statement_end_day": { + "type": "integer", + "description": "Statement end day of the card", + "minimum": 1, + "maximum": 31 + }, + "due_day": { + "type": "integer", + "description": "Due day of the card", + "minimum": 1, + "maximum": 31 + }, + "network": { + "type": "string", + "description": "Network of the card", + "enum": ["visa", "mastercard", "dinersclub", "amex", "rupay", "jcb", "discover"] + }, + "number": { + "type": "string", + "description": "Last 4 digits of the card number", + "maxLength": 4, + "minLength": 4, + "pattern": "^[0-9]{4}$" + } + }, + "required": [ + "account", + "credit_limit", + "statement_end_day", + "due_day", + "network", + "number" + ], + "additionalProperties": false + } } }, "required": ["journal_path", "db_path"], diff --git a/internal/server/credit_card.go b/internal/server/credit_card.go new file mode 100644 index 00000000..88918771 --- /dev/null +++ b/internal/server/credit_card.go @@ -0,0 +1,34 @@ +package server + +import ( + "github.com/ananthakumaran/paisa/internal/config" + "github.com/ananthakumaran/paisa/internal/model/posting" + "github.com/ananthakumaran/paisa/internal/query" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +type CreditCardSummary struct { + Account string `json:"account"` + Network string `json:"network"` + Number string `json:"number"` +} + +func GetCreditCards(db *gorm.DB) gin.H { + var creditCards []CreditCardSummary + + for _, creditCardConfig := range config.GetConfig().CreditCards { + ps := query.Init(db).Where("account = ?", creditCardConfig.Account).All() + creditCards = append(creditCards, buildCreditCard(creditCardConfig, ps)) + } + + return gin.H{"creditCards": creditCards} +} + +func buildCreditCard(creditCardConfig config.CreditCard, ps []posting.Posting) CreditCardSummary { + return CreditCardSummary{ + Account: creditCardConfig.Account, + Network: creditCardConfig.Network, + Number: creditCardConfig.Number, + } +} diff --git a/internal/server/server.go b/internal/server/server.go index 72cfa80d..7fd68a63 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -363,6 +363,10 @@ func Build(db *gorm.DB, enableCompression bool) *gin.Engine { c.JSON(200, goal.GetGoalDetails(db, c.Param("type"), c.Param("name"))) }) + router.GET("/api/credit_cards", func(c *gin.Context) { + c.JSON(200, GetCreditCards(db)) + }) + router.NoRoute(func(c *gin.Context) { c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(web.Index)) }) diff --git a/src/app.scss b/src/app.scss index ebc9300e..4fef2345 100644 --- a/src/app.scss +++ b/src/app.scss @@ -1088,3 +1088,27 @@ textarea:invalid { div.is-hoverable:hover { background-color: $white-bis; } + +// credit card + +.credit-card-container { + display: grid; + gap: 18px; + grid-template-columns: repeat(auto-fill, minmax(19rem, 25rem)); +} + +.credit-card { + aspect-ratio: 3.375/2.125; + border-radius: 0.7rem; + display: flex; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.15); +} + +.has-debossed-text { + background-color: $grey-lighter; + -webkit-background-clip: text; + -moz-background-clip: text; + background-clip: text; + color: transparent; + text-shadow: rgba($white, 0.5) 0px 3px 3px; +} diff --git a/src/lib/components/CreditCardNetwork.svelte b/src/lib/components/CreditCardNetwork.svelte new file mode 100644 index 00000000..c33edcbd --- /dev/null +++ b/src/lib/components/CreditCardNetwork.svelte @@ -0,0 +1,119 @@ + + +{#if name == "visa"} +