Skip to content

Commit b86934f

Browse files
authored
Merge pull request #501 from sawankshrma/team_evidence
feat: Team Evidence Store and UI
2 parents 39a85d2 + d084904 commit b86934f

29 files changed

+1063
-52
lines changed

angular.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116
},
117117
"defaultProject": "DSOMM",
118118
"cli": {
119-
"defaultCollection": "@angular-eslint/schematics"
119+
"defaultCollection": "@angular-eslint/schematics",
120+
"analytics": false
120121
}
121122
}

src/app/app.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ import { ReportComponent } from './pages/report/report.component';
3737
import { ReportConfigModalComponent } from './component/report-config-modal/report-config-modal.component';
3838
import { TeamSelectorComponent } from './component/team-selector/team-selector.component';
3939
import { ColResizeDirective } from './directive/col-resize.directive';
40+
import { AddEvidenceModalComponent } from './component/add-evidence-modal/add-evidence-modal.component';
41+
import { EvidencePanelComponent } from './component/evidence-panel/evidence-panel.component';
42+
import { ViewEvidenceModalComponent } from './component/view-evidence-modal/view-evidence-modal.component';
4043

4144
@NgModule({
4245
declarations: [
@@ -65,6 +68,9 @@ import { ColResizeDirective } from './directive/col-resize.directive';
6568
ReportConfigModalComponent,
6669
TeamSelectorComponent,
6770
ColResizeDirective,
71+
AddEvidenceModalComponent,
72+
EvidencePanelComponent,
73+
ViewEvidenceModalComponent,
6874
],
6975
imports: [
7076
BrowserModule,

src/app/component/activity-description/activity-description.component.html

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -285,26 +285,6 @@ <h3><b>Usefulness</b></h3>
285285
>
286286
</mat-expansion-panel>
287287

288-
<!-- <mat-expansion-panel>
289-
<mat-expansion-panel-header>
290-
<mat-panel-title>
291-
<b>Teams Evidence</b>
292-
</mat-panel-title>
293-
</mat-expansion-panel-header>
294-
<mat-accordion multi="true">
295-
<mat-expansion-panel
296-
id="teamsEvidence"
297-
*ngFor="let item of this.currentActivity.teamsEvidence | keyvalue">
298-
<mat-expansion-panel-header>
299-
<mat-panel-title>
300-
<b [innerHTML]="item.key"></b>
301-
</mat-panel-title>
302-
</mat-expansion-panel-header>
303-
<p [innerHTML]="item.value"></p>
304-
</mat-expansion-panel>
305-
</mat-accordion>
306-
</mat-expansion-panel> -->
307-
308288
<mat-expansion-panel
309289
[expanded]="true"
310290
#implementationPanel
@@ -370,6 +350,9 @@ <h4 class="tool-name" [innerHTML]="implement['name']"></h4>
370350
<p>No teams have started implementing this activity yet.</p>
371351
</ng-template>
372352
</mat-expansion-panel>
353+
354+
<app-evidence-panel [activityUuid]="currentActivity.uuid || ''" [expanded]="isNarrowScreen">
355+
</app-evidence-panel>
373356
</mat-accordion>
374357

375358
<div
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
.config-content {
2+
max-height: 70vh;
3+
overflow-y: auto;
4+
padding: 0 40px;
5+
background-color: var(--background-primary);
6+
}
7+
8+
.config-section {
9+
padding: 16px 0;
10+
}
11+
12+
.config-section h3 {
13+
margin: 0 0 4px 0;
14+
font-size: 1.1em;
15+
font-weight: 500;
16+
}
17+
18+
.config-hint {
19+
margin: 0 0 12px 0;
20+
font-size: 0.85em;
21+
color: var(--text-secondary);
22+
}
23+
24+
.required {
25+
color: #c62828;
26+
font-weight: bold;
27+
}
28+
29+
.select-all-actions {
30+
display: flex;
31+
gap: 8px;
32+
margin-bottom: 8px;
33+
}
34+
35+
.checkbox-grid {
36+
display: grid;
37+
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
38+
gap: 8px;
39+
}
40+
41+
.full-width {
42+
width: 100%;
43+
}
44+
45+
.form-row {
46+
display: flex;
47+
gap: 16px;
48+
flex-wrap: wrap;
49+
}
50+
51+
.form-row mat-form-field {
52+
flex: 1;
53+
min-width: 180px;
54+
}
55+
56+
.attachment-row {
57+
display: flex;
58+
align-items: center;
59+
gap: 12px;
60+
margin-bottom: 8px;
61+
}
62+
63+
.attachment-type {
64+
flex: 0 0 120px;
65+
}
66+
67+
.attachment-link {
68+
flex: 1;
69+
}
70+
71+
.field-error {
72+
font-size: 0.8em;
73+
margin-top: -12px;
74+
margin-bottom: 16px;
75+
display: block;
76+
}
77+
78+
mat-divider {
79+
margin: 4px 0;
80+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<h2 mat-dialog-title align="center">Add Evidence</h2>
2+
3+
<mat-dialog-content class="config-content">
4+
<app-team-selector
5+
[allTeams]="allTeams"
6+
[(selectedTeams)]="selectedTeams"
7+
[teamGroups]="teamGroups"
8+
(selectedTeamsChange)="onSelectedTeamsChange($event)"
9+
[type]="'add-evidence-config'">
10+
</app-team-selector>
11+
<mat-error *ngIf="teamsError" class="field-error" style="padding: 0 0 8px 0"
12+
>At least one team must be selected.</mat-error
13+
>
14+
15+
<mat-divider></mat-divider>
16+
17+
<!-- Title -->
18+
<div class="config-section">
19+
<h3>Evidence Details</h3>
20+
<mat-form-field appearance="outline" class="full-width">
21+
<mat-label>Title</mat-label>
22+
<input matInput [(ngModel)]="title" placeholder="e.g. Implemented SAST scanning" required />
23+
</mat-form-field>
24+
<mat-error *ngIf="titleError" class="field-error">Title is required.</mat-error>
25+
26+
<mat-form-field appearance="outline" class="full-width">
27+
<mat-label>Description</mat-label>
28+
<textarea
29+
matInput
30+
[(ngModel)]="description"
31+
rows="4"
32+
placeholder="Describe the evidence in detail..."></textarea>
33+
</mat-form-field>
34+
<mat-form-field appearance="outline">
35+
<mat-label>Reviewer</mat-label>
36+
<input matInput [(ngModel)]="reviewer" placeholder="e.g. Jane Doe" />
37+
</mat-form-field>
38+
</div>
39+
40+
<mat-divider></mat-divider>
41+
<!-- Attachments -->
42+
<div class="config-section">
43+
<h3>Attachments</h3>
44+
<p class="config-hint">Add links to supporting documents, images, or URLs.</p>
45+
<div *ngFor="let att of attachments; let i = index" class="attachment-row">
46+
<mat-form-field appearance="outline" class="attachment-type">
47+
<mat-label>Type</mat-label>
48+
<mat-select [(ngModel)]="att.type">
49+
<mat-option *ngFor="let aType of attachmentTypes" [value]="aType">
50+
{{ aType }}
51+
</mat-option>
52+
</mat-select>
53+
</mat-form-field>
54+
<mat-form-field appearance="outline" class="attachment-link">
55+
<mat-label>URL</mat-label>
56+
<input matInput [(ngModel)]="att.externalLink" placeholder="https://..." />
57+
</mat-form-field>
58+
<button mat-icon-button color="warn" (click)="removeAttachment(i)" matTooltip="Remove">
59+
<mat-icon>delete</mat-icon>
60+
</button>
61+
</div>
62+
<button mat-stroked-button color="primary" (click)="addAttachment()">
63+
<mat-icon>add</mat-icon> Add Attachment
64+
</button>
65+
</div>
66+
</mat-dialog-content>
67+
68+
<mat-dialog-actions align="end">
69+
<button mat-button (click)="onCancel()">Cancel</button>
70+
<button mat-raised-button color="primary" (click)="onSave()">Save Evidence</button>
71+
</mat-dialog-actions>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { Component, Inject } from '@angular/core';
2+
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
3+
import { EvidenceEntry, EvidenceStore } from '../../model/evidence-store';
4+
import { TeamGroups } from '../../model/types';
5+
6+
export interface AddEvidenceModalData {
7+
activityUuid: string;
8+
allTeams: string[];
9+
teamGroups: TeamGroups;
10+
}
11+
12+
@Component({
13+
selector: 'app-add-evidence-modal',
14+
templateUrl: './add-evidence-modal.component.html',
15+
styleUrls: ['./add-evidence-modal.component.css'],
16+
})
17+
export class AddEvidenceModalComponent {
18+
activityUuid: string;
19+
allTeams: string[];
20+
teamGroups: TeamGroups;
21+
22+
// Form fields
23+
selectedTeams: string[] = [];
24+
title: string = '';
25+
description: string = '';
26+
progress: string = '';
27+
evidenceRecorded: string = EvidenceStore.todayDateString();
28+
reviewer: string = '';
29+
attachments: { type: string; externalLink: string }[] = [];
30+
31+
// Validation
32+
teamsError: boolean = false;
33+
titleError: boolean = false;
34+
35+
attachmentTypes: string[] = ['document', 'image', 'link'];
36+
37+
constructor(
38+
public dialogRef: MatDialogRef<AddEvidenceModalComponent>,
39+
@Inject(MAT_DIALOG_DATA) public data: AddEvidenceModalData
40+
) {
41+
this.activityUuid = data.activityUuid;
42+
this.allTeams = data.allTeams;
43+
this.teamGroups = data.teamGroups || {};
44+
}
45+
46+
onSelectedTeamsChange(teams: string[]): void {
47+
this.selectedTeams = teams;
48+
this.teamsError = this.selectedTeams.length === 0;
49+
}
50+
51+
addAttachment(): void {
52+
this.attachments.push({ type: 'link', externalLink: '' });
53+
}
54+
55+
removeAttachment(index: number): void {
56+
this.attachments.splice(index, 1);
57+
}
58+
59+
onSave(): void {
60+
this.teamsError = this.selectedTeams.length === 0;
61+
this.titleError = !this.title.trim();
62+
63+
if (this.teamsError || this.titleError) {
64+
return;
65+
}
66+
67+
// Filter out empty attachments
68+
const validAttachments = this.attachments.filter(a => a.externalLink.trim());
69+
70+
const entry: EvidenceEntry = {
71+
id: EvidenceStore.generateId(),
72+
teams: [...this.selectedTeams],
73+
title: this.title.trim(),
74+
description: this.description.trim(),
75+
evidenceRecorded: this.evidenceRecorded,
76+
reviewer: this.reviewer.trim() || undefined,
77+
attachment: validAttachments.length > 0 ? validAttachments : undefined,
78+
};
79+
80+
this.dialogRef.close({ activityUuid: this.activityUuid, entry });
81+
}
82+
83+
onCancel(): void {
84+
this.dialogRef.close(null);
85+
}
86+
}

0 commit comments

Comments
 (0)