Skip to content

Commit 519bbd3

Browse files
authored
feat: implement workspace-scoped decisions (ref Sentinent-AI/Sentinent#11) (#7)
* feat(dashboard): implement dashboard (ref Sentinent-AI/Sentinent#8) * feat(decisions): implement workspace-scoped decisions (ref Sentinent-AI/Sentinent#11)
1 parent b12c5cc commit 519bbd3

22 files changed

Lines changed: 627 additions & 6 deletions

src/app/app.routes.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,25 @@ export const routes: Routes = [
1717
component: CreateWorkspace,
1818
canActivate: [authGuard]
1919
},
20+
{
21+
path: 'workspaces/:id',
22+
loadComponent: () => import('./components/workspace/workspace-details/workspace-details').then(m => m.WorkspaceDetailsComponent),
23+
canActivate: [authGuard],
24+
children: [
25+
{
26+
path: 'decisions',
27+
loadComponent: () => import('./components/decision-list/decision-list.component').then(m => m.DecisionListComponent)
28+
},
29+
{
30+
path: 'decisions/new',
31+
loadComponent: () => import('./components/decision-form/decision-form.component').then(m => m.DecisionFormComponent)
32+
},
33+
{
34+
path: 'decisions/:decisionId/edit',
35+
loadComponent: () => import('./components/decision-form/decision-form.component').then(m => m.DecisionFormComponent)
36+
},
37+
{ path: '', redirectTo: 'decisions', pathMatch: 'full' }
38+
]
39+
},
2040
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' }
2141
];

src/app/components/dashboard/dashboard.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ header {
1313
padding-bottom: 20px;
1414
}
1515

16+
nav {
17+
display: flex;
18+
align-items: center;
19+
gap: 20px;
20+
}
21+
22+
.workspace-card-link {
23+
text-decoration: none;
24+
color: inherit;
25+
}
26+
1627
.logout-btn {
1728
background-color: #f44336;
1829
color: white;

src/app/components/dashboard/dashboard.html

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ <h2>Your Workspaces</h2>
1111
</div>
1212

1313
<div class="workspace-list">
14-
<div *ngFor="let ws of workspaces" class="workspace-card">
15-
<h3>{{ ws.name }}</h3>
16-
<p>{{ ws.description }}</p>
17-
<small>Created: {{ ws.createdDate | date }}</small>
18-
</div>
14+
<a *ngFor="let ws of workspaces" [routerLink]="['/workspaces', ws.id]" class="workspace-card-link">
15+
<div class="workspace-card">
16+
<h3>{{ ws.name }}</h3>
17+
<p>{{ ws.description }}</p>
18+
<small>Created: {{ ws.createdDate | date }}</small>
19+
</div>
20+
</a>
1921
</div>
20-
22+
2123
<div *ngIf="workspaces.length === 0" class="empty-state">
2224
<p>No workspaces found. Create one to get started!</p>
2325
</div>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
.form-container {
2+
max-width: 600px;
3+
margin: 20px auto;
4+
padding: 20px;
5+
border: 1px solid #ddd;
6+
border-radius: 4px;
7+
background-color: #fff;
8+
}
9+
10+
.form-group {
11+
margin-bottom: 15px;
12+
}
13+
14+
.form-group label {
15+
display: block;
16+
margin-bottom: 5px;
17+
font-weight: bold;
18+
}
19+
20+
.form-control {
21+
width: 100%;
22+
padding: 8px;
23+
border: 1px solid #ced4da;
24+
border-radius: 4px;
25+
box-sizing: border-box;
26+
/* Ensures padding doesn't increase width */
27+
}
28+
29+
.form-control.is-invalid {
30+
border-color: #dc3545;
31+
}
32+
33+
.invalid-feedback {
34+
color: #dc3545;
35+
font-size: 12px;
36+
margin-top: 5px;
37+
}
38+
39+
.form-actions {
40+
display: flex;
41+
justify-content: flex-end;
42+
gap: 10px;
43+
margin-top: 20px;
44+
}
45+
46+
.btn-primary {
47+
background-color: #007bff;
48+
color: white;
49+
padding: 8px 16px;
50+
border: none;
51+
border-radius: 4px;
52+
cursor: pointer;
53+
}
54+
55+
.btn-primary:disabled {
56+
background-color: #a0c4ff;
57+
cursor: not-allowed;
58+
}
59+
60+
.btn-secondary {
61+
background-color: #6c757d;
62+
color: white;
63+
padding: 8px 16px;
64+
border: none;
65+
border-radius: 4px;
66+
cursor: pointer;
67+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<div class="form-container">
2+
<h2>{{ isEditMode ? 'Edit Decision' : 'Create New Decision' }}</h2>
3+
4+
<form [formGroup]="decisionForm" (ngSubmit)="onSubmit()">
5+
<div class="form-group">
6+
<label for="title">Title *</label>
7+
<input type="text" id="title" formControlName="title" class="form-control"
8+
[ngClass]="{'is-invalid': decisionForm.get('title')?.invalid && decisionForm.get('title')?.touched}">
9+
<div *ngIf="decisionForm.get('title')?.invalid && decisionForm.get('title')?.touched"
10+
class="invalid-feedback">
11+
Title is required.
12+
</div>
13+
</div>
14+
15+
<div class="form-group">
16+
<label for="description">Description</label>
17+
<textarea id="description" formControlName="description" class="form-control" rows="4"></textarea>
18+
</div>
19+
20+
<div class="form-group">
21+
<label for="status">Status</label>
22+
<select id="status" formControlName="status" class="form-control">
23+
<option value="DRAFT">Draft</option>
24+
<option value="OPEN">Open</option>
25+
<option value="CLOSED">Closed</option>
26+
</select>
27+
</div>
28+
29+
<div class="form-actions">
30+
<button type="button" routerLink="../" class="btn-secondary">Cancel</button>
31+
<button type="submit" [disabled]="decisionForm.invalid || isLoading" class="btn-primary">
32+
{{ isEditMode ? 'Update Decision' : 'Create Decision' }}
33+
</button>
34+
</div>
35+
</form>
36+
</div>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { Component, OnInit } from '@angular/core';
2+
import { CommonModule } from '@angular/common';
3+
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
4+
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
5+
import { DecisionService } from '../../services/decision.service';
6+
import { Decision } from '../../models/decision.model';
7+
8+
@Component({
9+
selector: 'app-decision-form',
10+
standalone: true,
11+
imports: [CommonModule, ReactiveFormsModule, RouterModule],
12+
templateUrl: './decision-form.component.html',
13+
styleUrls: ['./decision-form.component.css']
14+
})
15+
export class DecisionFormComponent implements OnInit {
16+
decisionForm: FormGroup;
17+
isEditMode = false;
18+
decisionId: string | null = null;
19+
isLoading = false;
20+
21+
constructor(
22+
private fb: FormBuilder,
23+
private decisionService: DecisionService,
24+
private route: ActivatedRoute,
25+
private router: Router
26+
) {
27+
this.decisionForm = this.fb.group({
28+
title: ['', Validators.required],
29+
description: [''],
30+
status: ['DRAFT', Validators.required]
31+
});
32+
}
33+
34+
ngOnInit(): void {
35+
// Get workspaceId from parent
36+
this.route.parent?.paramMap.subscribe(params => {
37+
const workspaceId = params.get('id');
38+
if (workspaceId) {
39+
this.decisionForm.patchValue({ workspaceId });
40+
}
41+
});
42+
43+
// Get decisionId from current route
44+
this.route.paramMap.subscribe(params => {
45+
this.decisionId = params.get('decisionId');
46+
if (this.decisionId) {
47+
this.isEditMode = true;
48+
this.loadDecision(this.decisionId);
49+
}
50+
});
51+
}
52+
53+
loadDecision(id: string): void {
54+
this.isLoading = true;
55+
this.decisionService.getDecision(id).subscribe(decision => {
56+
this.isLoading = false;
57+
if (decision) {
58+
this.decisionForm.patchValue({
59+
title: decision.title,
60+
description: decision.description,
61+
status: decision.status
62+
});
63+
} else {
64+
this.router.navigate(['../'], { relativeTo: this.route });
65+
}
66+
});
67+
}
68+
69+
onSubmit(): void {
70+
if (this.decisionForm.invalid) {
71+
return;
72+
}
73+
74+
const formValue = this.decisionForm.value;
75+
76+
if (this.isEditMode && this.decisionId) {
77+
this.decisionService.updateDecision(this.decisionId, formValue).subscribe(() => {
78+
this.router.navigate(['../../'], { relativeTo: this.route });
79+
});
80+
} else {
81+
// Include workspaceId from parent route if available
82+
const workspaceId = this.route.parent?.snapshot.paramMap.get('id');
83+
this.decisionService.createDecision({ ...formValue, workspaceId }).subscribe(() => {
84+
this.router.navigate(['../'], { relativeTo: this.route });
85+
});
86+
}
87+
}
88+
}

src/app/components/decision-form/decision-form.css

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>decision-form works!</p>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Component } from '@angular/core';
2+
3+
@Component({
4+
selector: 'app-decision-form',
5+
imports: [],
6+
templateUrl: './decision-form.html',
7+
styleUrl: './decision-form.css',
8+
})
9+
export class DecisionForm {
10+
11+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
.decision-container {
2+
padding: 20px;
3+
max-width: 800px;
4+
margin: 0 auto;
5+
}
6+
7+
.header {
8+
display: flex;
9+
justify-content: space-between;
10+
align-items: center;
11+
margin-bottom: 20px;
12+
}
13+
14+
.btn-primary {
15+
background-color: #007bff;
16+
color: white;
17+
padding: 10px 15px;
18+
text-decoration: none;
19+
border-radius: 4px;
20+
}
21+
22+
.btn-secondary {
23+
background-color: #6c757d;
24+
color: white;
25+
padding: 5px 10px;
26+
text-decoration: none;
27+
border-radius: 4px;
28+
margin-right: 5px;
29+
cursor: pointer;
30+
border: none;
31+
}
32+
33+
.btn-danger {
34+
background-color: #dc3545;
35+
color: white;
36+
padding: 5px 10px;
37+
border: none;
38+
border-radius: 4px;
39+
cursor: pointer;
40+
}
41+
42+
.decision-card {
43+
border: 1px solid #ddd;
44+
padding: 15px;
45+
margin-bottom: 10px;
46+
border-radius: 4px;
47+
background-color: #fff;
48+
}
49+
50+
.decision-header {
51+
display: flex;
52+
justify-content: space-between;
53+
align-items: center;
54+
margin-bottom: 10px;
55+
}
56+
57+
.decision-header h3 {
58+
margin: 0;
59+
}
60+
61+
.status-badge {
62+
padding: 3px 8px;
63+
border-radius: 12px;
64+
font-size: 12px;
65+
font-weight: bold;
66+
}
67+
68+
.status-badge.draft {
69+
background-color: #e2e3e5;
70+
color: #383d41;
71+
}
72+
73+
.status-badge.open {
74+
background-color: #d4edda;
75+
color: #155724;
76+
}
77+
78+
.status-badge.closed {
79+
background-color: #d1ecf1;
80+
color: #0c5460;
81+
}
82+
83+
.decision-meta {
84+
font-size: 12px;
85+
color: #666;
86+
margin-bottom: 10px;
87+
}

0 commit comments

Comments
 (0)