Compare commits
3 Commits
ca86818987
...
2e28ac0dae
Author | SHA1 | Date | |
---|---|---|---|
2e28ac0dae | |||
44e148fbb8 | |||
2b6dad709b |
@ -549,12 +549,12 @@ func TestCompleteTask(t *testing.T) {
|
|||||||
// Create two allowance goals
|
// Create two allowance goals
|
||||||
e.POST("/user/1/allowance").WithJSON(CreateAllowanceRequest{
|
e.POST("/user/1/allowance").WithJSON(CreateAllowanceRequest{
|
||||||
Name: "Test Allowance 1",
|
Name: "Test Allowance 1",
|
||||||
Target: 1000,
|
Target: 100,
|
||||||
Weight: 50,
|
Weight: 50,
|
||||||
}).Expect().Status(201)
|
}).Expect().Status(201)
|
||||||
e.POST("/user/1/allowance").WithJSON(CreateAllowanceRequest{
|
e.POST("/user/1/allowance").WithJSON(CreateAllowanceRequest{
|
||||||
Name: "Test Allowance 1",
|
Name: "Test Allowance 1",
|
||||||
Target: 1000,
|
Target: 10,
|
||||||
Weight: 25,
|
Weight: 25,
|
||||||
}).Expect().Status(201)
|
}).Expect().Status(201)
|
||||||
|
|
||||||
@ -568,11 +568,11 @@ func TestCompleteTask(t *testing.T) {
|
|||||||
allowances := e.GET("/user/1/allowance").Expect().Status(200).JSON().Array()
|
allowances := e.GET("/user/1/allowance").Expect().Status(200).JSON().Array()
|
||||||
allowances.Length().IsEqual(3)
|
allowances.Length().IsEqual(3)
|
||||||
allowances.Value(0).Object().Value("id").Number().IsEqual(0)
|
allowances.Value(0).Object().Value("id").Number().IsEqual(0)
|
||||||
allowances.Value(0).Object().Value("progress").Number().IsEqual(26)
|
allowances.Value(0).Object().Value("progress").Number().IsEqual(31)
|
||||||
allowances.Value(1).Object().Value("id").Number().IsEqual(1)
|
allowances.Value(1).Object().Value("id").Number().IsEqual(1)
|
||||||
allowances.Value(1).Object().Value("progress").Number().IsEqual(50)
|
allowances.Value(1).Object().Value("progress").Number().IsEqual(60)
|
||||||
allowances.Value(2).Object().Value("id").Number().IsEqual(2)
|
allowances.Value(2).Object().Value("id").Number().IsEqual(2)
|
||||||
allowances.Value(2).Object().Value("progress").Number().IsEqual(25)
|
allowances.Value(2).Object().Value("progress").Number().IsEqual(10)
|
||||||
|
|
||||||
// And also for user 2
|
// And also for user 2
|
||||||
allowances = e.GET("/user/2/allowance").Expect().Status(200).JSON().Array()
|
allowances = e.GET("/user/2/allowance").Expect().Status(200).JSON().Array()
|
||||||
|
@ -416,16 +416,20 @@ func (db *Db) CompleteTask(taskId int) error {
|
|||||||
|
|
||||||
if sumOfWeights > 0 {
|
if sumOfWeights > 0 {
|
||||||
// Distribute the reward to the allowances
|
// Distribute the reward to the allowances
|
||||||
for allowanceRow := range tx.Query("select id, weight from allowances where user_id = ? and weight > 0").Bind(userId).Range(&err) {
|
for allowanceRow := range tx.Query("select id, weight, target, balance from allowances where user_id = ? and weight > 0 order by (target - balance) asc").Bind(userId).Range(&err) {
|
||||||
var allowanceId int
|
var allowanceId, allowanceTarget, allowanceBalance int
|
||||||
var allowanceWeight float64
|
var allowanceWeight float64
|
||||||
err = allowanceRow.Scan(&allowanceId, &allowanceWeight)
|
err = allowanceRow.Scan(&allowanceId, &allowanceWeight, &allowanceTarget, &allowanceBalance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the amount to add to the allowance
|
// Calculate the amount to add to the allowance
|
||||||
amount := int((allowanceWeight / sumOfWeights) * float64(remainingReward))
|
amount := int((allowanceWeight / sumOfWeights) * float64(remainingReward))
|
||||||
|
if allowanceBalance+amount > allowanceTarget {
|
||||||
|
// If the amount reaches past the target, set it to the target
|
||||||
|
amount = allowanceTarget - allowanceBalance
|
||||||
|
}
|
||||||
sumOfWeights -= allowanceWeight
|
sumOfWeights -= allowanceWeight
|
||||||
err = tx.Query("update allowances set balance = balance + ? where id = ? and user_id = ?").
|
err = tx.Query("update allowances set balance = balance + ? where id = ? and user_id = ?").
|
||||||
Bind(amount, allowanceId, userId).Exec()
|
Bind(amount, allowanceId, userId).Exec()
|
||||||
|
107
backend/lite.go
107
backend/lite.go
@ -1,107 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ViewModel struct {
|
|
||||||
Users []User
|
|
||||||
CurrentUser int
|
|
||||||
Allowances []Allowance
|
|
||||||
Tasks []Task
|
|
||||||
History []History
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderLite(c *gin.Context) {
|
|
||||||
if c.Query("user") != "" {
|
|
||||||
c.SetCookie("user", c.Query("user"), 3600, "/", "localhost", false, true)
|
|
||||||
c.Redirect(http.StatusFound, "/")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
currentUserStr, err := c.Cookie("user")
|
|
||||||
if errors.Is(err, http.ErrNoCookie) {
|
|
||||||
renderNoUser(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
unsetUserCookie(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
currentUser, err := strconv.Atoi(currentUserStr)
|
|
||||||
if err != nil {
|
|
||||||
unsetUserCookie(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userExists, err := db.UserExists(currentUser)
|
|
||||||
if !userExists || err != nil {
|
|
||||||
unsetUserCookie(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
renderWithUser(c, currentUser)
|
|
||||||
}
|
|
||||||
|
|
||||||
func unsetUserCookie(c *gin.Context) {
|
|
||||||
c.SetCookie("user", "", -1, "/", "localhost", false, true)
|
|
||||||
c.Redirect(http.StatusFound, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderNoUser(c *gin.Context) {
|
|
||||||
users, err := db.GetUsers()
|
|
||||||
if err != nil {
|
|
||||||
c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "lite.gohtml", ViewModel{
|
|
||||||
Users: users,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderWithUser(c *gin.Context, currentUser int) {
|
|
||||||
users, err := db.GetUsers()
|
|
||||||
if err != nil {
|
|
||||||
c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
allowances, err := db.GetUserAllowances(currentUser)
|
|
||||||
if err != nil {
|
|
||||||
c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks, err := db.GetTasks()
|
|
||||||
if err != nil {
|
|
||||||
c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
history, err := db.GetHistory(currentUser)
|
|
||||||
if err != nil {
|
|
||||||
c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "lite.gohtml", ViewModel{
|
|
||||||
Users: users,
|
|
||||||
CurrentUser: currentUser,
|
|
||||||
Allowances: allowances,
|
|
||||||
Tasks: tasks,
|
|
||||||
History: history,
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
{{- /*gotype: allowance_planner.ViewModel*/}}
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>Allowance Planner 2000</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Allowance Planner 2000</h1>
|
|
||||||
<h2>Users</h2>
|
|
||||||
{{range .Users}}
|
|
||||||
{{if eq $.CurrentUser .ID}}
|
|
||||||
<strong>{{.Name}}</strong>
|
|
||||||
{{else}}
|
|
||||||
<a href="?user={{.ID}}">{{.Name}}</a>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if ne .CurrentUser 0}}
|
|
||||||
<h2>Allowances</h2>
|
|
||||||
<table border="1">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Progress</th>
|
|
||||||
<th>Target</th>
|
|
||||||
<th>Weight</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{range .Allowances}}
|
|
||||||
{{if eq .ID 0}}
|
|
||||||
<tr>
|
|
||||||
<td>Total</td>
|
|
||||||
<td>{{.Progress}}</td>
|
|
||||||
<td></td>
|
|
||||||
<td>{{.Weight}}</td>
|
|
||||||
</tr>
|
|
||||||
{{else}}
|
|
||||||
<tr>
|
|
||||||
<td>{{.Name}}</td>
|
|
||||||
<td>{{.Progress}}</td>
|
|
||||||
<td>{{.Target}}</td>
|
|
||||||
<td>{{.Weight}}</td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h2>Tasks</h2>
|
|
||||||
<table border="1">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Assigned</th>
|
|
||||||
<th>Reward</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{range .Tasks}}
|
|
||||||
<tr>
|
|
||||||
<td>{{.Name}}</td>
|
|
||||||
<td>{{.Assigned}}</td>
|
|
||||||
<td>{{.Reward}}</td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h2>History</h2>
|
|
||||||
<table border="1">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Timestamp</th>
|
|
||||||
<th>Allowance</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{range .History}}
|
|
||||||
<tr>
|
|
||||||
<td>{{.Timestamp}}</td>
|
|
||||||
<td>{{.Allowance}}</td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{{end}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -593,8 +593,7 @@ func start(ctx context.Context, config *ServerConfig) {
|
|||||||
router.Use(cors.New(corsConfig))
|
router.Use(cors.New(corsConfig))
|
||||||
|
|
||||||
// Web endpoints
|
// Web endpoints
|
||||||
router.LoadHTMLFiles("lite.gohtml")
|
loadWebEndpoints(router)
|
||||||
router.GET("/", renderLite)
|
|
||||||
// API endpoints
|
// API endpoints
|
||||||
router.GET("/api/users", getUsers)
|
router.GET("/api/users", getUsers)
|
||||||
router.GET("/api/user/:userId", getUser)
|
router.GET("/api/user/:userId", getUser)
|
||||||
@ -645,7 +644,7 @@ func start(ctx context.Context, config *ServerConfig) {
|
|||||||
func main() {
|
func main() {
|
||||||
config := ServerConfig{
|
config := ServerConfig{
|
||||||
Datasource: os.Getenv("DB_PATH"),
|
Datasource: os.Getenv("DB_PATH"),
|
||||||
Addr: ":8081",
|
Addr: ":8080",
|
||||||
}
|
}
|
||||||
if config.Datasource == "" {
|
if config.Datasource == "" {
|
||||||
config.Datasource = "allowance_planner.db3"
|
config.Datasource = "allowance_planner.db3"
|
||||||
|
228
backend/web.go
Normal file
228
backend/web.go
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ViewModel struct {
|
||||||
|
Users []User
|
||||||
|
CurrentUser int
|
||||||
|
Allowances []Allowance
|
||||||
|
Tasks []Task
|
||||||
|
History []History
|
||||||
|
Error string
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadWebEndpoints(router *gin.Engine) {
|
||||||
|
router.LoadHTMLFiles("web.gohtml")
|
||||||
|
router.GET("/", renderIndex)
|
||||||
|
router.GET("/login", renderLogin)
|
||||||
|
router.POST("/createTask", renderCreateTask)
|
||||||
|
router.GET("/completeTask", renderCompleteTask)
|
||||||
|
router.POST("/createAllowance", renderCreateAllowance)
|
||||||
|
router.GET("/completeAllowance", renderCompleteAllowance)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderLogin(c *gin.Context) {
|
||||||
|
if c.Query("user") != "" {
|
||||||
|
c.SetCookie("user", c.Query("user"), 3600, "/", "localhost", false, true)
|
||||||
|
}
|
||||||
|
c.Redirect(http.StatusFound, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderIndex(c *gin.Context) {
|
||||||
|
currentUser := getCurrentUser(c)
|
||||||
|
if currentUser == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
renderWithUser(c, *currentUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderCreateTask(c *gin.Context) {
|
||||||
|
currentUser := getCurrentUser(c)
|
||||||
|
if currentUser == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := c.PostForm("name")
|
||||||
|
rewardStr := c.PostForm("reward")
|
||||||
|
reward, err := strconv.Atoi(rewardStr)
|
||||||
|
if err != nil {
|
||||||
|
renderError(c, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if name == "" || reward <= 0 {
|
||||||
|
renderError(c, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.CreateTask(&CreateTaskRequest{
|
||||||
|
Name: name,
|
||||||
|
Reward: reward,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
renderError(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Redirect(http.StatusFound, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderCompleteTask(c *gin.Context) {
|
||||||
|
taskIDStr := c.Query("task")
|
||||||
|
taskID, err := strconv.Atoi(taskIDStr)
|
||||||
|
if err != nil {
|
||||||
|
renderError(c, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.CompleteTask(taskID)
|
||||||
|
if err != nil {
|
||||||
|
renderError(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Redirect(http.StatusFound, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderCreateAllowance(c *gin.Context) {
|
||||||
|
currentUser := getCurrentUser(c)
|
||||||
|
if currentUser == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := c.PostForm("name")
|
||||||
|
targetStr := c.PostForm("target")
|
||||||
|
target, err := strconv.Atoi(targetStr)
|
||||||
|
if err != nil {
|
||||||
|
renderError(c, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
weightStr := c.PostForm("weight")
|
||||||
|
weight, err := strconv.ParseFloat(weightStr, 64)
|
||||||
|
if err != nil {
|
||||||
|
renderError(c, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if name == "" || target <= 0 || weight <= 0 {
|
||||||
|
renderError(c, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.CreateAllowance(*currentUser, &CreateAllowanceRequest{
|
||||||
|
Name: name,
|
||||||
|
Target: target,
|
||||||
|
Weight: weight,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
renderError(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Redirect(http.StatusFound, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderCompleteAllowance(c *gin.Context) {
|
||||||
|
currentUser := getCurrentUser(c)
|
||||||
|
if currentUser == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
allowanceIDStr := c.Query("allowance")
|
||||||
|
allowanceID, err := strconv.Atoi(allowanceIDStr)
|
||||||
|
if err != nil {
|
||||||
|
renderError(c, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.CompleteAllowance(*currentUser, allowanceID)
|
||||||
|
if err != nil {
|
||||||
|
renderError(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Redirect(http.StatusFound, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentUser(c *gin.Context) *int {
|
||||||
|
currentUserStr, err := c.Cookie("user")
|
||||||
|
if errors.Is(err, http.ErrNoCookie) {
|
||||||
|
renderNoUser(c)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
unsetUserCookie(c)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
currentUser, err := strconv.Atoi(currentUserStr)
|
||||||
|
if err != nil {
|
||||||
|
unsetUserCookie(c)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
userExists, err := db.UserExists(currentUser)
|
||||||
|
if !userExists || err != nil {
|
||||||
|
unsetUserCookie(c)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ¤tUser
|
||||||
|
}
|
||||||
|
|
||||||
|
func unsetUserCookie(c *gin.Context) {
|
||||||
|
c.SetCookie("user", "", -1, "/", "localhost", false, true)
|
||||||
|
c.Redirect(http.StatusFound, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderNoUser(c *gin.Context) {
|
||||||
|
users, err := db.GetUsers()
|
||||||
|
if err != nil {
|
||||||
|
renderError(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "web.gohtml", ViewModel{
|
||||||
|
Users: users,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderWithUser(c *gin.Context, currentUser int) {
|
||||||
|
users, err := db.GetUsers()
|
||||||
|
if err != nil {
|
||||||
|
renderError(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
allowances, err := db.GetUserAllowances(currentUser)
|
||||||
|
if err != nil {
|
||||||
|
renderError(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks, err := db.GetTasks()
|
||||||
|
if err != nil {
|
||||||
|
renderError(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
history, err := db.GetHistory(currentUser)
|
||||||
|
if err != nil {
|
||||||
|
renderError(c, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "web.gohtml", ViewModel{
|
||||||
|
Users: users,
|
||||||
|
CurrentUser: currentUser,
|
||||||
|
Allowances: allowances,
|
||||||
|
Tasks: tasks,
|
||||||
|
History: history,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderError(c *gin.Context, statusCode int, err error) {
|
||||||
|
c.HTML(statusCode, "web.gohtml", ViewModel{
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
132
backend/web.gohtml
Normal file
132
backend/web.gohtml
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
{{- /*gotype: allowance_planner.ViewModel*/}}
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Allowance Planner 2000</title>
|
||||||
|
<style>
|
||||||
|
tr:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Allowance Planner 2000</h1>
|
||||||
|
|
||||||
|
{{if ne .Error ""}}
|
||||||
|
<h2>Error</h2>
|
||||||
|
<p>{{.Error}}</p>
|
||||||
|
{{else}}
|
||||||
|
<h2>Users</h2>
|
||||||
|
{{range .Users}}
|
||||||
|
{{if eq $.CurrentUser .ID}}
|
||||||
|
<strong>{{.Name}}</strong>
|
||||||
|
{{else}}
|
||||||
|
<a href="/login?user={{.ID}}">{{.Name}}</a>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if ne .CurrentUser 0}}
|
||||||
|
<h2>Allowances</h2>
|
||||||
|
<form action="/createAllowance" method="post">
|
||||||
|
<table border="1">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Progress</th>
|
||||||
|
<th>Target</th>
|
||||||
|
<th>Weight</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><label><input type="text" name="name" placeholder="Name"></label></td>
|
||||||
|
<td></td>
|
||||||
|
<td><label><input type="number" name="target" placeholder="Target"></label></td>
|
||||||
|
<td><label><input type="number" name="weight" placeholder="Weight"></label></td>
|
||||||
|
<td><button>Create</button></td>
|
||||||
|
</tr>
|
||||||
|
{{range .Allowances}}
|
||||||
|
{{if eq .ID 0}}
|
||||||
|
<tr>
|
||||||
|
<td>Total</td>
|
||||||
|
<td>{{.Progress}}</td>
|
||||||
|
<td></td>
|
||||||
|
<td>{{.Weight}}</td>
|
||||||
|
</tr>
|
||||||
|
{{else}}
|
||||||
|
<tr>
|
||||||
|
<td>{{.Name}}</td>
|
||||||
|
<td><progress max="{{.Target}}" value="{{.Progress}}"></progress> ({{.Progress}})</td>
|
||||||
|
<td>{{.Target}}</td>
|
||||||
|
<td>{{.Weight}}</td>
|
||||||
|
{{if ge .Progress .Target}}
|
||||||
|
<td>
|
||||||
|
<a href="/completeAllowance?allowance={{.ID}}">Mark as completed</a>
|
||||||
|
</td>
|
||||||
|
{{end}}
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h2>Tasks</h2>
|
||||||
|
<form method="post" action="/createTask">
|
||||||
|
<table border="1">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Assigned</th>
|
||||||
|
<th>Reward</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range .Tasks}}
|
||||||
|
<tr>
|
||||||
|
<td>{{.Name}}</td>
|
||||||
|
<td>
|
||||||
|
{{if eq .Assigned nil}}
|
||||||
|
None
|
||||||
|
{{else}}
|
||||||
|
{{.Assigned}}
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
<td>{{.Reward}}</td>
|
||||||
|
<td>
|
||||||
|
<a href="/completeTask?task={{.ID}}">Mark as completed</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
<tr>
|
||||||
|
<td><label><input type="text" name="name" placeholder="Name"></label></td>
|
||||||
|
<td></td>
|
||||||
|
<td><label><input type="number" name="reward" placeholder="Reward"></label></td>
|
||||||
|
<td><button>Create</button></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h2>History</h2>
|
||||||
|
<table border="1">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
<th>Allowance</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range .History}}
|
||||||
|
<tr>
|
||||||
|
<td>{{.Timestamp}}</td>
|
||||||
|
<td>{{.Allowance}}</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
x
Reference in New Issue
Block a user