From 8f0dbeb1581c5f1eb6b39592b2399eddc70c689e Mon Sep 17 00:00:00 2001 From: Anantha Kumaran Date: Sun, 21 Jan 2024 15:46:38 +0530 Subject: [PATCH] WIP: credit card --- internal/config/config.go | 12 ++ internal/config/schema.json | 66 +++++++++- internal/server/credit_card.go | 34 +++++ internal/server/server.go | 4 + src/app.scss | 24 ++++ src/lib/components/CreditCardNetwork.svelte | 119 ++++++++++++++++++ src/lib/components/Navbar.svelte | 1 + src/lib/utils.ts | 8 ++ .../liabilities/credit_card/+page.svelte | 91 ++++++++++++++ src/routes/(app)/more/goals/+page.svelte | 16 +-- 10 files changed, 364 insertions(+), 11 deletions(-) create mode 100644 internal/server/credit_card.go create mode 100644 src/lib/components/CreditCardNetwork.svelte create mode 100644 src/routes/(app)/liabilities/credit_card/+page.svelte 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"} +
+ +
+{/if} + +{#if name == "mastercard"} +
+ +
+{/if} + +{#if name == "dinersclub"} +
+ +
+{/if} + +{#if name == "amex"} +
+ +
+{/if} + +{#if name == "rupay"} +
+ +
+{/if} + +{#if name == "jcb"} +
+ +
+{/if} + +{#if name == "discover"} +
+ +
+{/if} diff --git a/src/lib/components/Navbar.svelte b/src/lib/components/Navbar.svelte index 9d551166..8da39f77 100644 --- a/src/lib/components/Navbar.svelte +++ b/src/lib/components/Navbar.svelte @@ -97,6 +97,7 @@ href: "/liabilities", children: [ { label: "Balance", href: "/balance" }, + { label: "Credit Card", href: "/credit_card", help: "credit-card" }, { label: "Repayment", href: "/repayment" }, { label: "Interest", href: "/interest" } ] diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 6340feb1..cf105402 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -481,6 +481,12 @@ export interface Log { msg: string; } +export interface CreditCardSummary { + account: string; + network: string; + number: string; +} + export interface GoalSummary { type: string; name: string; @@ -609,6 +615,8 @@ export function ajax(route: "/api/liabilities/interest"): Promise<{ interest_timeline_breakdown: Interest[]; }>; +export function ajax(route: "/api/credit_cards"): Promise<{ creditCards: CreditCardSummary[] }>; + export function ajax(route: "/api/goals"): Promise<{ goals: GoalSummary[] }>; export function ajax( route: "/api/goals/retirement/:name", diff --git a/src/routes/(app)/liabilities/credit_card/+page.svelte b/src/routes/(app)/liabilities/credit_card/+page.svelte new file mode 100644 index 00000000..4c1d2080 --- /dev/null +++ b/src/routes/(app)/liabilities/credit_card/+page.svelte @@ -0,0 +1,91 @@ + + +
+
+
+
+
+ {#each creditCards as creditCard} +
+
+
+ +
+
+ {iconText(creditCard.account)} + {restName(restName(creditCard.account))} +
+
+
+
+
+ Last bill +
+
+ {formatCurrency(Math.random() * 100000 * Math.random())} +
+
+ due in 3 days +
+
+
+
+ Credit usage +
+
+ {formatCurrency(Math.random() * 100000 * Math.random())} + 1% of {formatCurrency(450000)} + +
+
+
+
+
+ * * * *  * * * *  * * * *   {creditCard.number} +
+
+ +
+
+
+ {/each} +
+
+
+
+
+ + Oops! You haven't configured any goals yet. Checkout the + docs page to get started. + +
+
+
+
diff --git a/src/routes/(app)/more/goals/+page.svelte b/src/routes/(app)/more/goals/+page.svelte index f98fbe45..02eb4f0d 100644 --- a/src/routes/(app)/more/goals/+page.svelte +++ b/src/routes/(app)/more/goals/+page.svelte @@ -63,14 +63,6 @@
-
-
- - Oops! You haven't configured any goals yet. Checkout the - docs page to get started. - -
-
{/each}
+
+
+ + Oops! You haven't configured any goals yet. Checkout the + docs page to get started. + +
+