diff --git a/src/app/components/dashboard/dashboard.css b/src/app/components/dashboard/dashboard.css
index 84888f0..6f5c325 100644
--- a/src/app/components/dashboard/dashboard.css
+++ b/src/app/components/dashboard/dashboard.css
@@ -13,6 +13,17 @@ header {
padding-bottom: 20px;
}
+nav {
+ display: flex;
+ align-items: center;
+ gap: 20px;
+}
+
+.workspace-card-link {
+ text-decoration: none;
+ color: inherit;
+}
+
.logout-btn {
background-color: #f44336;
color: white;
diff --git a/src/app/components/dashboard/dashboard.html b/src/app/components/dashboard/dashboard.html
index 70e8929..fafcfb9 100644
--- a/src/app/components/dashboard/dashboard.html
+++ b/src/app/components/dashboard/dashboard.html
@@ -11,13 +11,15 @@
Your Workspaces
-
+
No workspaces found. Create one to get started!
diff --git a/src/app/components/decision-form/decision-form.component.css b/src/app/components/decision-form/decision-form.component.css
new file mode 100644
index 0000000..71e22d1
--- /dev/null
+++ b/src/app/components/decision-form/decision-form.component.css
@@ -0,0 +1,67 @@
+.form-container {
+ max-width: 600px;
+ margin: 20px auto;
+ padding: 20px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ background-color: #fff;
+}
+
+.form-group {
+ margin-bottom: 15px;
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: 5px;
+ font-weight: bold;
+}
+
+.form-control {
+ width: 100%;
+ padding: 8px;
+ border: 1px solid #ced4da;
+ border-radius: 4px;
+ box-sizing: border-box;
+ /* Ensures padding doesn't increase width */
+}
+
+.form-control.is-invalid {
+ border-color: #dc3545;
+}
+
+.invalid-feedback {
+ color: #dc3545;
+ font-size: 12px;
+ margin-top: 5px;
+}
+
+.form-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ margin-top: 20px;
+}
+
+.btn-primary {
+ background-color: #007bff;
+ color: white;
+ padding: 8px 16px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.btn-primary:disabled {
+ background-color: #a0c4ff;
+ cursor: not-allowed;
+}
+
+.btn-secondary {
+ background-color: #6c757d;
+ color: white;
+ padding: 8px 16px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/src/app/components/decision-form/decision-form.component.html b/src/app/components/decision-form/decision-form.component.html
new file mode 100644
index 0000000..68930f8
--- /dev/null
+++ b/src/app/components/decision-form/decision-form.component.html
@@ -0,0 +1,36 @@
+
\ No newline at end of file
diff --git a/src/app/components/decision-form/decision-form.component.ts b/src/app/components/decision-form/decision-form.component.ts
new file mode 100644
index 0000000..70adcba
--- /dev/null
+++ b/src/app/components/decision-form/decision-form.component.ts
@@ -0,0 +1,88 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { ActivatedRoute, Router, RouterModule } from '@angular/router';
+import { DecisionService } from '../../services/decision.service';
+import { Decision } from '../../models/decision.model';
+
+@Component({
+ selector: 'app-decision-form',
+ standalone: true,
+ imports: [CommonModule, ReactiveFormsModule, RouterModule],
+ templateUrl: './decision-form.component.html',
+ styleUrls: ['./decision-form.component.css']
+})
+export class DecisionFormComponent implements OnInit {
+ decisionForm: FormGroup;
+ isEditMode = false;
+ decisionId: string | null = null;
+ isLoading = false;
+
+ constructor(
+ private fb: FormBuilder,
+ private decisionService: DecisionService,
+ private route: ActivatedRoute,
+ private router: Router
+ ) {
+ this.decisionForm = this.fb.group({
+ title: ['', Validators.required],
+ description: [''],
+ status: ['DRAFT', Validators.required]
+ });
+ }
+
+ ngOnInit(): void {
+ // Get workspaceId from parent
+ this.route.parent?.paramMap.subscribe(params => {
+ const workspaceId = params.get('id');
+ if (workspaceId) {
+ this.decisionForm.patchValue({ workspaceId });
+ }
+ });
+
+ // Get decisionId from current route
+ this.route.paramMap.subscribe(params => {
+ this.decisionId = params.get('decisionId');
+ if (this.decisionId) {
+ this.isEditMode = true;
+ this.loadDecision(this.decisionId);
+ }
+ });
+ }
+
+ loadDecision(id: string): void {
+ this.isLoading = true;
+ this.decisionService.getDecision(id).subscribe(decision => {
+ this.isLoading = false;
+ if (decision) {
+ this.decisionForm.patchValue({
+ title: decision.title,
+ description: decision.description,
+ status: decision.status
+ });
+ } else {
+ this.router.navigate(['../'], { relativeTo: this.route });
+ }
+ });
+ }
+
+ onSubmit(): void {
+ if (this.decisionForm.invalid) {
+ return;
+ }
+
+ const formValue = this.decisionForm.value;
+
+ if (this.isEditMode && this.decisionId) {
+ this.decisionService.updateDecision(this.decisionId, formValue).subscribe(() => {
+ this.router.navigate(['../../'], { relativeTo: this.route });
+ });
+ } else {
+ // Include workspaceId from parent route if available
+ const workspaceId = this.route.parent?.snapshot.paramMap.get('id');
+ this.decisionService.createDecision({ ...formValue, workspaceId }).subscribe(() => {
+ this.router.navigate(['../'], { relativeTo: this.route });
+ });
+ }
+ }
+}
diff --git a/src/app/components/decision-form/decision-form.css b/src/app/components/decision-form/decision-form.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/decision-form/decision-form.html b/src/app/components/decision-form/decision-form.html
new file mode 100644
index 0000000..7e9c4ac
--- /dev/null
+++ b/src/app/components/decision-form/decision-form.html
@@ -0,0 +1 @@
+decision-form works!
diff --git a/src/app/components/decision-form/decision-form.ts b/src/app/components/decision-form/decision-form.ts
new file mode 100644
index 0000000..0a59e66
--- /dev/null
+++ b/src/app/components/decision-form/decision-form.ts
@@ -0,0 +1,11 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-decision-form',
+ imports: [],
+ templateUrl: './decision-form.html',
+ styleUrl: './decision-form.css',
+})
+export class DecisionForm {
+
+}
diff --git a/src/app/components/decision-list/decision-list.component.css b/src/app/components/decision-list/decision-list.component.css
new file mode 100644
index 0000000..781008a
--- /dev/null
+++ b/src/app/components/decision-list/decision-list.component.css
@@ -0,0 +1,87 @@
+.decision-container {
+ padding: 20px;
+ max-width: 800px;
+ margin: 0 auto;
+}
+
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
+.btn-primary {
+ background-color: #007bff;
+ color: white;
+ padding: 10px 15px;
+ text-decoration: none;
+ border-radius: 4px;
+}
+
+.btn-secondary {
+ background-color: #6c757d;
+ color: white;
+ padding: 5px 10px;
+ text-decoration: none;
+ border-radius: 4px;
+ margin-right: 5px;
+ cursor: pointer;
+ border: none;
+}
+
+.btn-danger {
+ background-color: #dc3545;
+ color: white;
+ padding: 5px 10px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.decision-card {
+ border: 1px solid #ddd;
+ padding: 15px;
+ margin-bottom: 10px;
+ border-radius: 4px;
+ background-color: #fff;
+}
+
+.decision-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 10px;
+}
+
+.decision-header h3 {
+ margin: 0;
+}
+
+.status-badge {
+ padding: 3px 8px;
+ border-radius: 12px;
+ font-size: 12px;
+ font-weight: bold;
+}
+
+.status-badge.draft {
+ background-color: #e2e3e5;
+ color: #383d41;
+}
+
+.status-badge.open {
+ background-color: #d4edda;
+ color: #155724;
+}
+
+.status-badge.closed {
+ background-color: #d1ecf1;
+ color: #0c5460;
+}
+
+.decision-meta {
+ font-size: 12px;
+ color: #666;
+ margin-bottom: 10px;
+}
\ No newline at end of file
diff --git a/src/app/components/decision-list/decision-list.component.html b/src/app/components/decision-list/decision-list.component.html
new file mode 100644
index 0000000..4196208
--- /dev/null
+++ b/src/app/components/decision-list/decision-list.component.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+ No decisions found. Create one to get started!
+
+
+
+
+
{{ decision.description || 'No description provided.' }}
+
+ Created: {{ decision.createdAt | date:'shortDate' }}
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/components/decision-list/decision-list.component.ts b/src/app/components/decision-list/decision-list.component.ts
new file mode 100644
index 0000000..17b4681
--- /dev/null
+++ b/src/app/components/decision-list/decision-list.component.ts
@@ -0,0 +1,38 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ActivatedRoute, RouterModule } from '@angular/router';
+import { Decision } from '../../models/decision.model';
+import { DecisionService } from '../../services/decision.service';
+import { Observable } from 'rxjs';
+
+@Component({
+ selector: 'app-decision-list',
+ standalone: true,
+ imports: [CommonModule, RouterModule],
+ templateUrl: './decision-list.component.html',
+ styleUrls: ['./decision-list.component.css']
+})
+export class DecisionListComponent implements OnInit {
+ decisions$: Observable | undefined;
+
+ constructor(
+ private decisionService: DecisionService,
+ private route: ActivatedRoute
+ ) { }
+
+ ngOnInit(): void {
+ // Get workspaceId from the parent route (workspaces/:id)
+ this.route.parent?.paramMap.subscribe(params => {
+ const workspaceId = params.get('id');
+ if (workspaceId) {
+ this.decisions$ = this.decisionService.getDecisions(workspaceId);
+ }
+ });
+ }
+
+ deleteDecision(id: string): void {
+ if (confirm('Are you sure you want to delete this decision?')) {
+ this.decisionService.deleteDecision(id);
+ }
+ }
+}
diff --git a/src/app/components/decision-list/decision-list.css b/src/app/components/decision-list/decision-list.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/decision-list/decision-list.html b/src/app/components/decision-list/decision-list.html
new file mode 100644
index 0000000..0c2fbfa
--- /dev/null
+++ b/src/app/components/decision-list/decision-list.html
@@ -0,0 +1 @@
+decision-list works!
diff --git a/src/app/components/decision-list/decision-list.ts b/src/app/components/decision-list/decision-list.ts
new file mode 100644
index 0000000..732933e
--- /dev/null
+++ b/src/app/components/decision-list/decision-list.ts
@@ -0,0 +1,11 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-decision-list',
+ imports: [],
+ templateUrl: './decision-list.html',
+ styleUrl: './decision-list.css',
+})
+export class DecisionList {
+
+}
diff --git a/src/app/components/workspace/create-workspace-placeholder.ts b/src/app/components/workspace/create-workspace-placeholder.ts
new file mode 100644
index 0000000..e785b87
--- /dev/null
+++ b/src/app/components/workspace/create-workspace-placeholder.ts
@@ -0,0 +1,16 @@
+import { Component } from '@angular/core';
+import { RouterLink } from '@angular/router';
+
+@Component({
+ selector: 'app-create-workspace-placeholder',
+ standalone: true,
+ imports: [RouterLink],
+ template: `
+
+
Create Workspace
+
This feature is being implemented in Issue #4.
+
Back to Dashboard
+
+ `
+})
+export class CreateWorkspacePlaceholder { }
diff --git a/src/app/components/workspace/workspace-details/workspace-details.css b/src/app/components/workspace/workspace-details/workspace-details.css
new file mode 100644
index 0000000..ec5419e
--- /dev/null
+++ b/src/app/components/workspace/workspace-details/workspace-details.css
@@ -0,0 +1,46 @@
+.workspace-details-container {
+ padding: 20px;
+ max-width: 1000px;
+ margin: 0 auto;
+}
+
+header {
+ margin-bottom: 20px;
+ border-bottom: 1px solid #eee;
+ padding-bottom: 20px;
+}
+
+header h1 {
+ margin: 0 0 10px 0;
+}
+
+header p {
+ color: #666;
+ margin: 0;
+}
+
+.workspace-nav {
+ display: flex;
+ gap: 20px;
+ border-bottom: 1px solid #ddd;
+ margin-bottom: 20px;
+}
+
+.workspace-nav a {
+ padding: 10px 15px;
+ text-decoration: none;
+ color: #555;
+ border-bottom: 2px solid transparent;
+}
+
+.workspace-nav a.active {
+ color: #007bff;
+ border-bottom-color: #007bff;
+ font-weight: 500;
+}
+
+.loading {
+ text-align: center;
+ padding: 40px;
+ color: #666;
+}
\ No newline at end of file
diff --git a/src/app/components/workspace/workspace-details/workspace-details.html b/src/app/components/workspace/workspace-details/workspace-details.html
new file mode 100644
index 0000000..6cf2abf
--- /dev/null
+++ b/src/app/components/workspace/workspace-details/workspace-details.html
@@ -0,0 +1,19 @@
+
+
+
+ Loading workspace...
+
\ No newline at end of file
diff --git a/src/app/components/workspace/workspace-details/workspace-details.ts b/src/app/components/workspace/workspace-details/workspace-details.ts
new file mode 100644
index 0000000..6863e45
--- /dev/null
+++ b/src/app/components/workspace/workspace-details/workspace-details.ts
@@ -0,0 +1,31 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ActivatedRoute, RouterModule, RouterLink, RouterOutlet } from '@angular/router';
+import { WorkspaceService } from '../../../services/workspace';
+import { Workspace } from '../../../models/workspace';
+import { Observable } from 'rxjs';
+
+@Component({
+ selector: 'app-workspace-details',
+ standalone: true,
+ imports: [CommonModule, RouterModule, RouterLink, RouterOutlet],
+ templateUrl: './workspace-details.html',
+ styleUrls: ['./workspace-details.css']
+})
+export class WorkspaceDetailsComponent implements OnInit {
+ workspace$: Observable | undefined;
+
+ constructor(
+ private route: ActivatedRoute,
+ private workspaceService: WorkspaceService
+ ) { }
+
+ ngOnInit(): void {
+ this.route.paramMap.subscribe(params => {
+ const id = params.get('id');
+ if (id) {
+ this.workspace$ = this.workspaceService.getWorkspace(id);
+ }
+ });
+ }
+}
diff --git a/src/app/models/decision.model.ts b/src/app/models/decision.model.ts
new file mode 100644
index 0000000..515b0c1
--- /dev/null
+++ b/src/app/models/decision.model.ts
@@ -0,0 +1,11 @@
+export interface Decision {
+ id: string;
+ title: string;
+ description?: string;
+ status: 'DRAFT' | 'OPEN' | 'CLOSED';
+ workspaceId: string;
+ userId: string;
+ createdAt: Date;
+ updatedAt: Date;
+ isDeleted: boolean;
+}
diff --git a/src/app/services/decision.service.ts b/src/app/services/decision.service.ts
new file mode 100644
index 0000000..3e1aced
--- /dev/null
+++ b/src/app/services/decision.service.ts
@@ -0,0 +1,94 @@
+import { Injectable } from '@angular/core';
+import { BehaviorSubject, Observable, of } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { Decision } from '../models/decision.model';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class DecisionService {
+ private decisions: Decision[] = [];
+ private decisionsSubject = new BehaviorSubject([]);
+
+ constructor() {
+ // Initialize with some mock data
+ this.decisions = [
+ {
+ id: '1',
+ title: 'Choose Frontend Framework',
+ description: 'Decide between Angular and React',
+ status: 'CLOSED',
+ workspaceId: 'ws-1',
+ userId: 'user-1',
+ createdAt: new Date('2023-10-25'),
+ updatedAt: new Date('2023-10-26'),
+ isDeleted: false
+ },
+ {
+ id: '2',
+ title: 'Database Selection',
+ description: 'Evaluate SQLite vs PostgreSQL for local dev',
+ status: 'OPEN',
+ workspaceId: 'ws-1',
+ userId: 'user-1',
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ isDeleted: false
+ }
+ ];
+ this.decisionsSubject.next(this.decisions);
+ }
+
+ getDecisions(workspaceId: string): Observable {
+ return this.decisionsSubject.asObservable().pipe(
+ map(decisions => decisions.filter(d => d.workspaceId === workspaceId && !d.isDeleted))
+ );
+ }
+
+ getDecision(id: string): Observable {
+ return this.decisionsSubject.asObservable().pipe(
+ map(decisions => decisions.find(d => d.id === id))
+ );
+ }
+
+ createDecision(decision: Partial): Observable {
+ const newDecision: Decision = {
+ id: Math.random().toString(36).substring(2, 9),
+ title: decision.title!,
+ description: decision.description,
+ status: decision.status || 'DRAFT',
+ workspaceId: decision.workspaceId || 'ws-1', // Default to ws-1 for now
+ userId: 'user-1', // Mock user
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ isDeleted: false
+ };
+
+ this.decisions.push(newDecision);
+ this.decisionsSubject.next(this.decisions);
+ return of(newDecision);
+ }
+
+ updateDecision(id: string, updates: Partial): Observable {
+ const index = this.decisions.findIndex(d => d.id === id);
+ if (index !== -1) {
+ this.decisions[index] = {
+ ...this.decisions[index],
+ ...updates,
+ updatedAt: new Date()
+ };
+ this.decisionsSubject.next(this.decisions);
+ return of(this.decisions[index]);
+ }
+ return of(undefined);
+ }
+
+ deleteDecision(id: string): Observable {
+ const index = this.decisions.findIndex(d => d.id === id);
+ if (index !== -1) {
+ this.decisions[index].isDeleted = true;
+ this.decisionsSubject.next(this.decisions);
+ }
+ return of(void 0);
+ }
+}
diff --git a/src/app/services/workspace.ts b/src/app/services/workspace.ts
index 2065013..3895d13 100644
--- a/src/app/services/workspace.ts
+++ b/src/app/services/workspace.ts
@@ -34,6 +34,10 @@ export class WorkspaceService {
return of(this.mockWorkspaces);
}
+ getWorkspace(id: string): Observable {
+ return of(this.mockWorkspaces.find(w => w.id === id));
+ }
+
createWorkspace(name: string): Observable {
const newWorkspace: Workspace = {
id: Math.random().toString(36).substring(7),