feat: add finance module with yearly income/expense tracking
- FinanceEntry model (income/expense x fixed/extra) - CRUD API endpoints for finance entries - FinancePage with summary cards and drill-down - Updated navigation with Finance tab - Updated todos default filter to active
This commit is contained in:
@@ -5,18 +5,20 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"lifelog/internal/model"
|
||||
"lifelog/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
todoSvc *service.TodoService
|
||||
dailyLogSvc *service.DailyLogService
|
||||
todoSvc *service.TodoService
|
||||
dailyLogSvc *service.DailyLogService
|
||||
financeSvc *service.FinanceService
|
||||
}
|
||||
|
||||
func NewHandler(todoSvc *service.TodoService, dailyLogSvc *service.DailyLogService) *Handler {
|
||||
return &Handler{todoSvc: todoSvc, dailyLogSvc: dailyLogSvc}
|
||||
func NewHandler(todoSvc *service.TodoService, dailyLogSvc *service.DailyLogService, financeSvc *service.FinanceService) *Handler {
|
||||
return &Handler{todoSvc: todoSvc, dailyLogSvc: dailyLogSvc, financeSvc: financeSvc}
|
||||
}
|
||||
|
||||
// Todo handlers
|
||||
@@ -165,3 +167,95 @@ func (h *Handler) SaveDailyLog(c *gin.Context) {
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": log})
|
||||
}
|
||||
|
||||
// Finance handlers
|
||||
|
||||
func (h *Handler) GetFinanceByYear(c *gin.Context) {
|
||||
yearStr := c.Param("year")
|
||||
year, err := strconv.Atoi(yearStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid year"})
|
||||
return
|
||||
}
|
||||
entries, err := h.financeSvc.GetByYear(year)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": entries})
|
||||
}
|
||||
|
||||
func (h *Handler) CreateFinanceEntry(c *gin.Context) {
|
||||
var req struct {
|
||||
Year int `json:"year" binding:"required"`
|
||||
Month int `json:"month"`
|
||||
Type string `json:"type" binding:"required"` // income / expense
|
||||
Category string `json:"category" binding:"required"` // fixed / extra
|
||||
Description string `json:"description"`
|
||||
Amount float64 `json:"amount" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
entry := &model.FinanceEntry{
|
||||
Year: req.Year,
|
||||
Month: req.Month,
|
||||
Type: req.Type,
|
||||
Category: req.Category,
|
||||
Description: req.Description,
|
||||
Amount: req.Amount,
|
||||
}
|
||||
if err := h.financeSvc.Create(entry); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, gin.H{"data": entry})
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateFinanceEntry(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Year int `json:"year"`
|
||||
Month int `json:"month"`
|
||||
Type string `json:"type"`
|
||||
Category string `json:"category"`
|
||||
Description string `json:"description"`
|
||||
Amount float64 `json:"amount"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
err = h.financeSvc.Update(&model.FinanceEntry{
|
||||
ID: id,
|
||||
Year: req.Year,
|
||||
Month: req.Month,
|
||||
Type: req.Type,
|
||||
Category: req.Category,
|
||||
Description: req.Description,
|
||||
Amount: req.Amount,
|
||||
})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "updated"})
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteFinanceEntry(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
if err := h.financeSvc.Delete(id); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "deleted"})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user