Compare commits
5 Commits
f4c8ae33dd
...
19/post-al
| Author | SHA1 | Date | |
|---|---|---|---|
| b2f532fa22 | |||
| b56738653d | |||
| 5d803bb01c | |||
| 2620d6ee47 | |||
| 74536bd49d |
@@ -2,15 +2,8 @@
|
|||||||
An improved Allowance Planner app.
|
An improved Allowance Planner app.
|
||||||
|
|
||||||
## Running backend
|
## Running backend
|
||||||
In order to run the backend, go to the `backend` directory and run:
|
In order to run the backend, go to the `backend directory and run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ go run .
|
$ go run .
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running frontend
|
|
||||||
In order to run the frontend, go to the `allowance-planner-v2` directory in the `frontend` directory and run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ ionic serve
|
|
||||||
```
|
|
||||||
|
|||||||
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@@ -1,3 +1,2 @@
|
|||||||
*.db3
|
*.db3
|
||||||
*.db3-*
|
*.db3-*
|
||||||
/allowance_planner
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
gitea.seeseepuff.be/seeseemelk/mysqlite v0.12.0 h1:kl0VFgvm52UKxJhZpf1hvucxZdOoXY50g/VmzsWH+/8=
|
||||||
|
gitea.seeseepuff.be/seeseemelk/mysqlite v0.12.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE=
|
||||||
|
gitea.seeseepuff.be/seeseemelk/mysqlite v0.13.0 h1:nqSXu5i5fHB1rrx/kfi8Phn/J6eFa2yh02FiGc9U1yg=
|
||||||
|
gitea.seeseepuff.be/seeseemelk/mysqlite v0.13.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE=
|
||||||
gitea.seeseepuff.be/seeseemelk/mysqlite v0.14.0 h1:aRItVfUj48fBmuec7rm/jY9KCfvHW2VzJfItVk4t8sw=
|
gitea.seeseepuff.be/seeseemelk/mysqlite v0.14.0 h1:aRItVfUj48fBmuec7rm/jY9KCfvHW2VzJfItVk4t8sw=
|
||||||
gitea.seeseepuff.be/seeseemelk/mysqlite v0.14.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE=
|
gitea.seeseepuff.be/seeseemelk/mysqlite v0.14.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE=
|
||||||
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4=
|
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4=
|
||||||
@@ -214,10 +218,14 @@ modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8=
|
|||||||
modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
|
modernc.org/libc v1.65.6 h1:OhJUhmuJ6MVZdqL5qmnd0/my46DKGFhSX4WOR7ijfyE=
|
||||||
|
modernc.org/libc v1.65.6/go.mod h1:MOiGAM9lrMBT9L8xT1nO41qYl5eg9gCp9/kWhz5L7WA=
|
||||||
modernc.org/libc v1.65.7 h1:Ia9Z4yzZtWNtUIuiPuQ7Qf7kxYrxP1/jeHZzG8bFu00=
|
modernc.org/libc v1.65.7 h1:Ia9Z4yzZtWNtUIuiPuQ7Qf7kxYrxP1/jeHZzG8bFu00=
|
||||||
modernc.org/libc v1.65.7/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU=
|
modernc.org/libc v1.65.7/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
|
modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4=
|
||||||
|
modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
|
|||||||
@@ -587,14 +587,9 @@ func start(ctx context.Context, config *ServerConfig) {
|
|||||||
defer db.db.MustClose()
|
defer db.db.MustClose()
|
||||||
|
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
router.Use(cors.New(cors.Config{
|
||||||
corsConfig := cors.DefaultConfig()
|
AllowOrigins: []string{"*"},
|
||||||
corsConfig.AllowAllOrigins = true
|
}))
|
||||||
router.Use(cors.New(corsConfig))
|
|
||||||
|
|
||||||
// Web endpoints
|
|
||||||
loadWebEndpoints(router)
|
|
||||||
// API endpoints
|
|
||||||
router.GET("/api/users", getUsers)
|
router.GET("/api/users", getUsers)
|
||||||
router.GET("/api/user/:userId", getUser)
|
router.GET("/api/user/:userId", getUser)
|
||||||
router.POST("/api/user/:userId/history", postHistory)
|
router.POST("/api/user/:userId/history", postHistory)
|
||||||
@@ -644,7 +639,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"
|
||||||
|
|||||||
168
backend/web.go
168
backend/web.go
@@ -1,168 +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 loadWebEndpoints(router *gin.Engine) {
|
|
||||||
router.LoadHTMLFiles("web.gohtml")
|
|
||||||
router.GET("/", renderIndex)
|
|
||||||
router.GET("/login", renderLogin)
|
|
||||||
router.POST("/createTask", renderCreateTask)
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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 renderCreateTask(c *gin.Context) {
|
|
||||||
currentUserStr, err := c.Cookie("user")
|
|
||||||
if errors.Is(err, http.ErrNoCookie) {
|
|
||||||
c.HTML(http.StatusBadRequest, "error.gohtml", gin.H{
|
|
||||||
"error": "User not logged in",
|
|
||||||
})
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
name := c.PostForm("name")
|
|
||||||
rewardStr := c.PostForm("reward")
|
|
||||||
reward, err := strconv.Atoi(rewardStr)
|
|
||||||
if err != nil {
|
|
||||||
c.HTML(http.StatusBadRequest, "error.gohtml", gin.H{
|
|
||||||
"error": "Invalid reward value",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if name == "" || reward <= 0 {
|
|
||||||
c.HTML(http.StatusBadRequest, "error.gohtml", gin.H{
|
|
||||||
"error": "Name and reward must be provided",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.CreateTask(&CreateTaskRequest{
|
|
||||||
Name: name,
|
|
||||||
Reward: reward,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
c.HTML(http.StatusInternalServerError, "error.gohtml", gin.H{
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
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, "web.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, "web.gohtml", ViewModel{
|
|
||||||
Users: users,
|
|
||||||
CurrentUser: currentUser,
|
|
||||||
Allowances: allowances,
|
|
||||||
Tasks: tasks,
|
|
||||||
History: history,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,98 +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="/login?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>
|
|
||||||
<form method="post" action="/createTask">
|
|
||||||
<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}}
|
|
||||||
<tr>
|
|
||||||
<td><label><input type="text" placeholder="Name"></label></td>
|
|
||||||
<td></td>
|
|
||||||
<td>
|
|
||||||
<label><input type="number" placeholder="Reward"></label>
|
|
||||||
<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}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -11,7 +11,6 @@ const routes: Routes = [
|
|||||||
path: '',
|
path: '',
|
||||||
loadChildren: () => import('./pages/tabs/tabs.module').then(m => m.TabsPageModule)
|
loadChildren: () => import('./pages/tabs/tabs.module').then(m => m.TabsPageModule)
|
||||||
},
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ import { BrowserModule } from '@angular/platform-browser';
|
|||||||
import { RouteReuseStrategy } from '@angular/router';
|
import { RouteReuseStrategy } from '@angular/router';
|
||||||
|
|
||||||
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
|
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
|
||||||
import { Drivers } from '@ionic/storage';
|
import { Drivers, Storage } from '@ionic/storage';
|
||||||
import { IonicStorageModule } from '@ionic/storage-angular';
|
import { IonicStorageModule } from '@ionic/storage-angular';
|
||||||
|
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AppComponent],
|
declarations: [AppComponent],
|
||||||
@@ -16,7 +15,6 @@ import { ReactiveFormsModule } from '@angular/forms';
|
|||||||
BrowserModule,
|
BrowserModule,
|
||||||
IonicModule.forRoot(),
|
IonicModule.forRoot(),
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
ReactiveFormsModule,
|
|
||||||
IonicStorageModule.forRoot({
|
IonicStorageModule.forRoot({
|
||||||
name: '__mydb',
|
name: '__mydb',
|
||||||
driverOrder: [Drivers.IndexedDB, Drivers.LocalStorage]
|
driverOrder: [Drivers.IndexedDB, Drivers.LocalStorage]
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ export interface Task {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
reward: number;
|
reward: number;
|
||||||
assigned: number | null;
|
assigned: number;
|
||||||
}
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
|
||||||
|
|
||||||
import { EditTaskPage } from './edit-task.page';
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: EditTaskPage,
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [RouterModule.forChild(routes)],
|
|
||||||
exports: [RouterModule],
|
|
||||||
})
|
|
||||||
export class EditTaskPageRoutingModule {}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
||||||
|
|
||||||
import { IonicModule } from '@ionic/angular';
|
|
||||||
|
|
||||||
import { EditTaskPageRoutingModule } from './edit-task-routing.module';
|
|
||||||
|
|
||||||
import { EditTaskPage } from './edit-task.page';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
FormsModule,
|
|
||||||
IonicModule,
|
|
||||||
EditTaskPageRoutingModule,
|
|
||||||
ReactiveFormsModule
|
|
||||||
],
|
|
||||||
declarations: [EditTaskPage]
|
|
||||||
})
|
|
||||||
export class EditTaskPageModule {}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<ion-header [translucent]="true">
|
|
||||||
<ion-toolbar>
|
|
||||||
<div class="toolbar">
|
|
||||||
<ion-title *ngIf="isAddMode">Create Task</ion-title>
|
|
||||||
<ion-title *ngIf="!isAddMode">Edit Task</ion-title>
|
|
||||||
<button
|
|
||||||
*ngIf="!isAddMode"
|
|
||||||
class="remove-button"
|
|
||||||
(click)="deleteTask()"
|
|
||||||
>Delete task</button>
|
|
||||||
</div>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content [fullscreen]="true">
|
|
||||||
<form [formGroup]="form">
|
|
||||||
<label>Task Name</label>
|
|
||||||
<input id="name" type="text" formControlName="name"/>
|
|
||||||
|
|
||||||
<label>Reward</label>
|
|
||||||
<input id="name" type="number" formControlName="reward"/>
|
|
||||||
|
|
||||||
<label>Assigned</label>
|
|
||||||
<select formControlName="assigned">
|
|
||||||
<option *ngFor="let user of users" [value]="user.id">{{ user.name }}</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<button type="button" [disabled]="!form.valid" (click)="submit()">
|
|
||||||
<span *ngIf="isAddMode">Add Task</span>
|
|
||||||
<span *ngIf="!isAddMode">Update Task</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</ion-content>
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
.toolbar {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-button {
|
|
||||||
background-color: var(--ion-color-primary);
|
|
||||||
margin-right: 15px;
|
|
||||||
width: 85px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
color: var(--ion-color-primary);
|
|
||||||
margin-top: 25px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
select {
|
|
||||||
border: 1px solid var(--ion-color-primary);
|
|
||||||
border-radius: 5px;
|
|
||||||
width: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background-color: var(--ion-color-primary);
|
|
||||||
border-radius: 5px;
|
|
||||||
color: white;
|
|
||||||
padding: 10px;
|
|
||||||
width: 250px;
|
|
||||||
margin-top: auto;
|
|
||||||
margin-bottom: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:disabled,
|
|
||||||
button[disabled]{
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
import { EditTaskPage } from './edit-task.page';
|
|
||||||
|
|
||||||
describe('EditTaskPage', () => {
|
|
||||||
let component: EditTaskPage;
|
|
||||||
let fixture: ComponentFixture<EditTaskPage>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(EditTaskPage);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import { Location } from '@angular/common';
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
import { User } from 'src/app/models/user';
|
|
||||||
import { TaskService } from 'src/app/services/task.service';
|
|
||||||
import { UserService } from 'src/app/services/user.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-edit-task',
|
|
||||||
templateUrl: './edit-task.page.html',
|
|
||||||
styleUrls: ['./edit-task.page.scss'],
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class EditTaskPage implements OnInit {
|
|
||||||
form: FormGroup;
|
|
||||||
id: number;
|
|
||||||
isAddMode: boolean;
|
|
||||||
users: Array<User> = [{id: 0, name: 'unassigned'}];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private route: ActivatedRoute,
|
|
||||||
private formBuilder: FormBuilder,
|
|
||||||
private taskService: TaskService,
|
|
||||||
private userService: UserService,
|
|
||||||
private router: Router
|
|
||||||
) {
|
|
||||||
this.id = this.route.snapshot.params['id'];
|
|
||||||
this.isAddMode = !this.id;
|
|
||||||
|
|
||||||
this.form = this.formBuilder.group({
|
|
||||||
name: ['', Validators.required],
|
|
||||||
reward: ['', [Validators.required, Validators.pattern("^[0-9]*$")]],
|
|
||||||
assigned: [0, Validators.required]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.userService.getUserList().subscribe(users => {
|
|
||||||
this.users.push(...users);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!this.isAddMode) {
|
|
||||||
this.taskService.getTaskById(this.id).subscribe(task => {
|
|
||||||
this.form.setValue({
|
|
||||||
name: task.name,
|
|
||||||
reward: task.reward,
|
|
||||||
assigned: task.assigned !== null ? task.assigned : 0
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
submit() {
|
|
||||||
const formValue = this.form.value;
|
|
||||||
let assigned: number | null = Number(formValue.assigned);
|
|
||||||
if (assigned === 0) {
|
|
||||||
assigned = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const task = {
|
|
||||||
name: formValue.name,
|
|
||||||
reward: formValue.reward,
|
|
||||||
assigned
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isAddMode) {
|
|
||||||
this.taskService.createTask(task);
|
|
||||||
} else {
|
|
||||||
this.taskService.updateTask(this.id, task);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.router.navigate(['/tabs/tasks']);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteTask() {
|
|
||||||
this.taskService.deleteTask(this.id);
|
|
||||||
this.router.navigate(['/tabs/tasks']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,7 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'tasks',
|
path: 'tasks',
|
||||||
loadChildren: () => import('../tasks/tasks.module').then(m => m.TasksPageModule),
|
loadChildren: () => import('../tasks/tasks.module').then(m => m.TasksPageModule)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: TasksPage,
|
component: TasksPage,
|
||||||
},
|
}
|
||||||
{ path: 'add', loadChildren: () => import('../edit-task/edit-task.module').then(m => m.EditTaskPageModule) },
|
|
||||||
{ path: 'edit/:id', loadChildren: () => import('../edit-task/edit-task.module').then(m => m.EditTaskPageModule) }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@@ -1,30 +1,23 @@
|
|||||||
<ion-header [translucent]="true" class="ion-no-border">
|
<ion-header [translucent]="true" class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<div class="toolbar">
|
<ion-title>
|
||||||
<ion-title>
|
Tasks
|
||||||
Tasks
|
</ion-title>
|
||||||
</ion-title>
|
|
||||||
<button class="add-button" (click)="createTask()">Add task</button>
|
|
||||||
</div>
|
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<div class="content">
|
<div class="icon">
|
||||||
<div class="icon">
|
<mat-icon>filter_alt</mat-icon>
|
||||||
<mat-icon>filter_alt</mat-icon>
|
</div>
|
||||||
</div>
|
<div class="list">
|
||||||
<div class="list">
|
<div class="task" *ngFor="let task of tasks">
|
||||||
<div class="task" *ngFor="let task of tasks$ | async">
|
<button>Done</button>
|
||||||
<button (click)="completeTask(task.id)">Done</button>
|
<div class="name">{{ task.name }}</div>
|
||||||
<div (click)="updateTask(task.id)" class="item">
|
<div
|
||||||
<div class="name">{{ task.name }}</div>
|
class="reward"
|
||||||
<div
|
[ngClass]="{ 'negative': task.reward < 0 }"
|
||||||
class="reward"
|
>{{ task.reward.toFixed(2) }} SP</div>
|
||||||
[ngClass]="{ 'negative': task.reward < 0 }"
|
|
||||||
>{{ task.reward.toFixed(2) }} SP</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -1,13 +1,3 @@
|
|||||||
.toolbar {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -33,13 +23,6 @@ mat-icon {
|
|||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
color: var(--font-color);
|
color: var(--font-color);
|
||||||
@@ -62,9 +45,3 @@ button {
|
|||||||
color: white;
|
color: white;
|
||||||
background: var(--confirm-button-color);
|
background: var(--confirm-button-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-button {
|
|
||||||
background-color: var(--ion-color-primary);
|
|
||||||
margin-right: 15px;
|
|
||||||
width: 75px;
|
|
||||||
}
|
|
||||||
@@ -1,50 +1,24 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { TaskService } from 'src/app/services/task.service';
|
import { TaskService } from 'src/app/services/task.service';
|
||||||
import { Task } from 'src/app/models/task';
|
import { Task } from 'src/app/models/task';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
|
||||||
import { ViewWillEnter } from '@ionic/angular';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tasks',
|
selector: 'app-tasks',
|
||||||
templateUrl: 'tasks.page.html',
|
templateUrl: 'tasks.page.html',
|
||||||
styleUrls: ['tasks.page.scss'],
|
styleUrls: ['tasks.page.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
standalone: false,
|
standalone: false,
|
||||||
})
|
})
|
||||||
export class TasksPage implements ViewWillEnter {
|
export class TasksPage implements OnInit {
|
||||||
public tasks$: BehaviorSubject<Array<Task>> = new BehaviorSubject<Array<Task>>([]);
|
public tasks: Array<Task> = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private taskService: TaskService,
|
private taskService: TaskService
|
||||||
private router: Router,
|
) {}
|
||||||
private route: ActivatedRoute
|
|
||||||
) {
|
ngOnInit(): void {
|
||||||
this.getTasks();
|
this.taskService.getTaskList().subscribe(tasks => {
|
||||||
|
this.tasks = tasks;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ionViewWillEnter(): void {
|
|
||||||
this.getTasks();
|
|
||||||
}
|
|
||||||
|
|
||||||
getTasks() {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.taskService.getTaskList().subscribe(tasks => {
|
|
||||||
this.tasks$.next(tasks);
|
|
||||||
});
|
|
||||||
}, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
createTask() {
|
|
||||||
this.router.navigate(['add'], { relativeTo: this.route });
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTask(id: number) {
|
|
||||||
this.router.navigate(['edit', id], { relativeTo: this.route });
|
|
||||||
}
|
|
||||||
|
|
||||||
completeTask(id: number) {
|
|
||||||
this.taskService.completeTask(id);
|
|
||||||
this.getTasks();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,31 +7,10 @@ import { Task } from '../models/task';
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class TaskService {
|
export class TaskService {
|
||||||
private url = 'http://localhost:8080/api';
|
private url = 'http://localhost:8080/api'
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
getTaskList(): Observable<Array<Task>> {
|
getTaskList(): Observable<Array<Task>> {
|
||||||
return this.http.get<Task[]>(`${this.url}/tasks`);
|
return this.http.get<Task[]>(`${this.url}/tasks`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTaskById(taskId: number): Observable<Task> {
|
|
||||||
return this.http.get<Task>(`${this.url}/task/${taskId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
createTask(task: Partial<Task>) {
|
|
||||||
this.http.post(`${this.url}/tasks`, task).subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTask(id: number, task: Partial<Task>) {
|
|
||||||
this.http.put(`${this.url}/task/${id}`, task).subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
completeTask(id: number) {
|
|
||||||
this.http.post(`${this.url}/task/${id}/complete`, {}).subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteTask(id: number) {
|
|
||||||
this.http.delete(`${this.url}/task/${id}`).subscribe();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user