diff --git a/README.md b/README.md index 4638ab0..9760ed8 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,15 @@ An improved Allowance Planner app. ## 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 $ 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 +``` diff --git a/backend/main.go b/backend/main.go index 2102399..49a8a0d 100644 --- a/backend/main.go +++ b/backend/main.go @@ -587,9 +587,11 @@ func start(ctx context.Context, config *ServerConfig) { defer db.db.MustClose() router := gin.Default() - router.Use(cors.New(cors.Config{ - AllowOrigins: []string{"*"}, - })) + + corsConfig := cors.DefaultConfig() + corsConfig.AllowAllOrigins = true + router.Use(cors.New(corsConfig)) + router.GET("/api/users", getUsers) router.GET("/api/user/:userId", getUser) router.POST("/api/user/:userId/history", postHistory) diff --git a/frontend/allowance-planner-v2/src/app/app-routing.module.ts b/frontend/allowance-planner-v2/src/app/app-routing.module.ts index ffe16e8..2750f02 100644 --- a/frontend/allowance-planner-v2/src/app/app-routing.module.ts +++ b/frontend/allowance-planner-v2/src/app/app-routing.module.ts @@ -11,6 +11,7 @@ const routes: Routes = [ path: '', loadChildren: () => import('./pages/tabs/tabs.module').then(m => m.TabsPageModule) }, + ]; @NgModule({ imports: [ diff --git a/frontend/allowance-planner-v2/src/app/app.module.ts b/frontend/allowance-planner-v2/src/app/app.module.ts index a6c06b7..64e58db 100644 --- a/frontend/allowance-planner-v2/src/app/app.module.ts +++ b/frontend/allowance-planner-v2/src/app/app.module.ts @@ -3,11 +3,12 @@ import { BrowserModule } from '@angular/platform-browser'; import { RouteReuseStrategy } from '@angular/router'; import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; -import { Drivers, Storage } from '@ionic/storage'; +import { Drivers } from '@ionic/storage'; import { IonicStorageModule } from '@ionic/storage-angular'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; +import { ReactiveFormsModule } from '@angular/forms'; @NgModule({ declarations: [AppComponent], @@ -15,6 +16,7 @@ import { AppComponent } from './app.component'; BrowserModule, IonicModule.forRoot(), AppRoutingModule, + ReactiveFormsModule, IonicStorageModule.forRoot({ name: '__mydb', driverOrder: [Drivers.IndexedDB, Drivers.LocalStorage] diff --git a/frontend/allowance-planner-v2/src/app/models/task.ts b/frontend/allowance-planner-v2/src/app/models/task.ts index c2a3ca4..377a5d8 100644 --- a/frontend/allowance-planner-v2/src/app/models/task.ts +++ b/frontend/allowance-planner-v2/src/app/models/task.ts @@ -2,5 +2,5 @@ export interface Task { id: number; name: string; reward: number; - assigned: number; + assigned: number | null; } \ No newline at end of file diff --git a/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task-routing.module.ts b/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task-routing.module.ts new file mode 100644 index 0000000..cd1cb56 --- /dev/null +++ b/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task-routing.module.ts @@ -0,0 +1,17 @@ +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 {} diff --git a/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task.module.ts b/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task.module.ts new file mode 100644 index 0000000..6e427b8 --- /dev/null +++ b/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task.module.ts @@ -0,0 +1,21 @@ +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 {} diff --git a/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task.page.html b/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task.page.html new file mode 100644 index 0000000..8e046da --- /dev/null +++ b/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task.page.html @@ -0,0 +1,26 @@ + + + Create Task + Edit Task + + + + +
+ + + + + + + + + + +
+
diff --git a/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task.page.scss b/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task.page.scss new file mode 100644 index 0000000..e58999e --- /dev/null +++ b/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task.page.scss @@ -0,0 +1,34 @@ +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; +} \ No newline at end of file diff --git a/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task.page.spec.ts b/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task.page.spec.ts new file mode 100644 index 0000000..5887782 --- /dev/null +++ b/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task.page.spec.ts @@ -0,0 +1,17 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { EditTaskPage } from './edit-task.page'; + +describe('EditTaskPage', () => { + let component: EditTaskPage; + let fixture: ComponentFixture; + + beforeEach(() => { + fixture = TestBed.createComponent(EditTaskPage); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task.page.ts b/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task.page.ts new file mode 100644 index 0000000..65c78ce --- /dev/null +++ b/frontend/allowance-planner-v2/src/app/pages/edit-task/edit-task.page.ts @@ -0,0 +1,73 @@ +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 = [{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 + }) + }); + } + } + + 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.router.navigate(['/tabs/tasks']); + } +} diff --git a/frontend/allowance-planner-v2/src/app/pages/tabs/tabs-routing.module.ts b/frontend/allowance-planner-v2/src/app/pages/tabs/tabs-routing.module.ts index 52d6683..60ba0ae 100644 --- a/frontend/allowance-planner-v2/src/app/pages/tabs/tabs-routing.module.ts +++ b/frontend/allowance-planner-v2/src/app/pages/tabs/tabs-routing.module.ts @@ -17,7 +17,7 @@ const routes: Routes = [ }, { path: 'tasks', - loadChildren: () => import('../tasks/tasks.module').then(m => m.TasksPageModule) + loadChildren: () => import('../tasks/tasks.module').then(m => m.TasksPageModule), }, { path: '', diff --git a/frontend/allowance-planner-v2/src/app/pages/tasks/tasks-routing.module.ts b/frontend/allowance-planner-v2/src/app/pages/tasks/tasks-routing.module.ts index a3945ac..6ade771 100644 --- a/frontend/allowance-planner-v2/src/app/pages/tasks/tasks-routing.module.ts +++ b/frontend/allowance-planner-v2/src/app/pages/tasks/tasks-routing.module.ts @@ -6,7 +6,9 @@ const routes: Routes = [ { path: '', 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({ diff --git a/frontend/allowance-planner-v2/src/app/pages/tasks/tasks.page.html b/frontend/allowance-planner-v2/src/app/pages/tasks/tasks.page.html index 5521342..7507d9b 100644 --- a/frontend/allowance-planner-v2/src/app/pages/tasks/tasks.page.html +++ b/frontend/allowance-planner-v2/src/app/pages/tasks/tasks.page.html @@ -1,23 +1,28 @@ - - Tasks - +
+ + Tasks + + +
-
- filter_alt -
-
-
- -
{{ task.name }}
-
{{ task.reward.toFixed(2) }} SP
+
+
+ filter_alt +
+
+
+ +
{{ task.name }}
+
{{ task.reward.toFixed(2) }} SP
+
diff --git a/frontend/allowance-planner-v2/src/app/pages/tasks/tasks.page.scss b/frontend/allowance-planner-v2/src/app/pages/tasks/tasks.page.scss index aa15767..999a793 100644 --- a/frontend/allowance-planner-v2/src/app/pages/tasks/tasks.page.scss +++ b/frontend/allowance-planner-v2/src/app/pages/tasks/tasks.page.scss @@ -1,3 +1,13 @@ +.toolbar { + display: flex; +} + +.content { + display: flex; + flex-direction: column; + height: 100%; +} + .icon { padding: 5px; display: flex; @@ -44,4 +54,10 @@ button { border-radius: 10px; color: white; background: var(--confirm-button-color); +} + +.add-button { + background-color: var(--ion-color-primary); + margin-right: 15px; + width: 75px; } \ No newline at end of file diff --git a/frontend/allowance-planner-v2/src/app/pages/tasks/tasks.page.ts b/frontend/allowance-planner-v2/src/app/pages/tasks/tasks.page.ts index acec48c..f5970db 100644 --- a/frontend/allowance-planner-v2/src/app/pages/tasks/tasks.page.ts +++ b/frontend/allowance-planner-v2/src/app/pages/tasks/tasks.page.ts @@ -1,24 +1,39 @@ -import { Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { TaskService } from 'src/app/services/task.service'; import { Task } from 'src/app/models/task'; +import { ActivatedRoute, Router } from '@angular/router'; +import { BehaviorSubject } from 'rxjs'; +import { ViewWillEnter } from '@ionic/angular'; @Component({ selector: 'app-tasks', templateUrl: 'tasks.page.html', styleUrls: ['tasks.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, }) -export class TasksPage implements OnInit { - public tasks: Array = []; +export class TasksPage implements ViewWillEnter { + public tasks$: BehaviorSubject> = new BehaviorSubject>([]); constructor( - private taskService: TaskService - ) {} + private taskService: TaskService, + private router: Router, + private route: ActivatedRoute + ) { + this.getTasks(); + } - ngOnInit(): void { + ionViewWillEnter(): void { + this.getTasks(); + } + + getTasks() { this.taskService.getTaskList().subscribe(tasks => { - this.tasks = tasks; + this.tasks$.next(tasks); }); } + createTask() { + this.router.navigate(['add'], { relativeTo: this.route }); + } } diff --git a/frontend/allowance-planner-v2/src/app/services/task.service.ts b/frontend/allowance-planner-v2/src/app/services/task.service.ts index 5d4890c..ed1179b 100644 --- a/frontend/allowance-planner-v2/src/app/services/task.service.ts +++ b/frontend/allowance-planner-v2/src/app/services/task.service.ts @@ -7,10 +7,19 @@ import { Task } from '../models/task'; providedIn: 'root' }) export class TaskService { - private url = 'http://localhost:8080/api' + private url = 'http://localhost:8080/api'; + constructor(private http: HttpClient) {} getTaskList(): Observable> { return this.http.get(`${this.url}/tasks`); } + + getTaskById(taskId: number): Observable { + return this.http.get(`${this.url}/task/${taskId}`); + } + + createTask(task: Partial) { + this.http.post(`${this.url}/tasks`, task).subscribe(); + } } \ No newline at end of file