Skip to content

Commit 6f09967

Browse files
committed
add evidence data model in the new EvidenceStore
1 parent 03db749 commit 6f09967

File tree

3 files changed

+207
-0
lines changed

3 files changed

+207
-0
lines changed

src/app/model/data-store.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@ import { ActivityStore } from './activity-store';
22
import { Progress } from './types';
33
import { MetaStore, MetaStrings } from './meta-store';
44
import { ProgressStore } from './progress-store';
5+
import { EvidenceData, EvidenceStore } from './evidence-store';
56

67
export class DataStore {
78
public meta: MetaStore | null = null;
89
public activityStore: ActivityStore | null = null;
910
public progressStore: ProgressStore | null = null;
11+
public evidenceStore: EvidenceStore | null = null;
1012

1113
constructor() {
1214
this.meta = new MetaStore();
1315
this.activityStore = new ActivityStore();
1416
this.progressStore = new ProgressStore();
17+
this.evidenceStore = new EvidenceStore();
1518
}
1619

1720
public addActivities(activities: ActivityStore): void {
@@ -20,6 +23,9 @@ export class DataStore {
2023
public addProgressData(progress: Progress): void {
2124
this.progressStore?.addProgressData(progress);
2225
}
26+
public addEvidenceData(evidence: EvidenceData): void {
27+
this.evidenceStore?.addEvidenceData(evidence);
28+
}
2329

2430
public getMetaStrings(): MetaStrings {
2531
if (this.meta == null) {

src/app/model/evidence-store.ts

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import { YamlService } from '../service/yaml-loader/yaml-loader.service';
2+
import { Uuid } from './types';
3+
4+
export interface EvidenceAttachment {
5+
type: string; // e.g. 'document', 'image', 'link'
6+
externalLink: string; // URL
7+
}
8+
9+
export interface EvidenceEntry {
10+
id: string; // stable UUID for this entry
11+
teams: string[];
12+
title: string;
13+
evidenceRecorded: string; // ISO date string
14+
reviewer?: string;
15+
description: string;
16+
attachment?: EvidenceAttachment[];
17+
}
18+
19+
export type EvidenceData = Record<Uuid, EvidenceEntry[]>;
20+
21+
const LOCALSTORAGE_KEY: string = 'evidence';
22+
23+
export class EvidenceStore {
24+
private yamlService: YamlService = new YamlService();
25+
private _evidence: EvidenceData = {};
26+
27+
// ─── Lifecycle ────────────────────────────────────────────
28+
29+
public initFromLocalStorage(): void {
30+
const stored = this.retrieveStoredEvidence();
31+
if (stored) {
32+
this.addEvidenceData(stored);
33+
}
34+
}
35+
36+
// ─── Accessors ────────────────────────────────────────────
37+
38+
public getEvidenceData(): EvidenceData {
39+
return this._evidence;
40+
}
41+
42+
public getEvidence(activityUuid: Uuid): EvidenceEntry[] {
43+
return this._evidence[activityUuid] || [];
44+
}
45+
46+
public hasEvidence(activityUuid: Uuid): boolean {
47+
return (this._evidence[activityUuid]?.length || 0) > 0;
48+
}
49+
50+
public getEvidenceCount(activityUuid: Uuid): number {
51+
return this._evidence[activityUuid]?.length ?? 0;
52+
}
53+
54+
public getTotalEvidenceCount(): number {
55+
let count = 0;
56+
for (const uuid in this._evidence) {
57+
count += this._evidence[uuid].length;
58+
}
59+
return count;
60+
}
61+
62+
public getActivityUuidsWithEvidence(): Uuid[] {
63+
return Object.keys(this._evidence).filter(uuid => this._evidence[uuid].length > 0);
64+
}
65+
66+
// ─── Mutators ────────────────────────────────────────────
67+
68+
public addEvidenceData(newEvidence: EvidenceData): void {
69+
if (!newEvidence) return;
70+
71+
for (const activityUuid in newEvidence) {
72+
if (!this._evidence[activityUuid]) {
73+
this._evidence[activityUuid] = [];
74+
}
75+
76+
const newEntries = newEvidence[activityUuid];
77+
if (Array.isArray(newEntries)) {
78+
for (const entry of newEntries) {
79+
if (!this.isDuplicateEntry(activityUuid, entry)) {
80+
this._evidence[activityUuid].push(entry);
81+
}
82+
}
83+
}
84+
}
85+
}
86+
87+
public replaceEvidenceData(data: EvidenceData): void {
88+
this._evidence = data;
89+
this.saveToLocalStorage();
90+
}
91+
92+
public addEvidence(activityUuid: Uuid, entry: EvidenceEntry): void {
93+
if (!this._evidence[activityUuid]) {
94+
this._evidence[activityUuid] = [];
95+
}
96+
this._evidence[activityUuid].push(entry);
97+
this.saveToLocalStorage();
98+
}
99+
100+
public updateEvidence(
101+
activityUuid: Uuid,
102+
entryId: string,
103+
updatedEntry: Partial<EvidenceEntry>
104+
): void {
105+
const entries = this._evidence[activityUuid];
106+
if (!entries) {
107+
console.warn(`No evidence found for activity ${activityUuid}`);
108+
return;
109+
}
110+
const index = entries.findIndex(e => e.id === entryId);
111+
if (index === -1) {
112+
console.warn(`Cannot find evidence with id ${entryId} for activity ${activityUuid}`);
113+
return;
114+
}
115+
// Immutable update for Angular change detection
116+
entries[index] = { ...entries[index], ...updatedEntry };
117+
this.saveToLocalStorage();
118+
}
119+
120+
public deleteEvidence(activityUuid: Uuid, entryId: string): void {
121+
const entries = this._evidence[activityUuid];
122+
if (!entries) {
123+
console.warn(`No evidence found for activity ${activityUuid}`);
124+
return;
125+
}
126+
const index = entries.findIndex(e => e.id === entryId);
127+
if (index === -1) {
128+
console.warn(`Cannot find evidence with id ${entryId} for activity ${activityUuid}`);
129+
return;
130+
}
131+
entries.splice(index, 1);
132+
133+
if (entries.length === 0) {
134+
delete this._evidence[activityUuid];
135+
}
136+
this.saveToLocalStorage();
137+
}
138+
139+
public renameTeam(oldName: string, newName: string): void {
140+
console.log(`Renaming team '${oldName}' to '${newName}' in evidence store`);
141+
for (const uuid in this._evidence) {
142+
this._evidence[uuid].forEach(entry => {
143+
entry.teams = entry.teams.map(t => (t === oldName ? newName : t));
144+
});
145+
}
146+
this.saveToLocalStorage();
147+
}
148+
149+
// ─── Serialization ──────────────────────────────────────
150+
151+
public asYamlString(): string {
152+
return this.yamlService.stringify({ evidence: this._evidence });
153+
}
154+
155+
public saveToLocalStorage(): void {
156+
const yamlStr = this.asYamlString();
157+
localStorage.setItem(LOCALSTORAGE_KEY, yamlStr);
158+
}
159+
160+
public deleteBrowserStoredEvidence(): void {
161+
console.log('Deleting evidence from browser storage');
162+
localStorage.removeItem(LOCALSTORAGE_KEY);
163+
}
164+
165+
public retrieveStoredEvidenceYaml(): string | null {
166+
return localStorage.getItem(LOCALSTORAGE_KEY);
167+
}
168+
169+
public retrieveStoredEvidence(): EvidenceData | null {
170+
const yamlStr = this.retrieveStoredEvidenceYaml();
171+
if (!yamlStr) return null;
172+
173+
const parsed = this.yamlService.parse(yamlStr);
174+
return parsed?.evidence ?? null;
175+
}
176+
177+
// ─── Helpers ─────────────────────────────────────────────
178+
179+
private isDuplicateEntry(activityUuid: Uuid, entry: EvidenceEntry): boolean {
180+
const existing = this._evidence[activityUuid];
181+
if (!existing) return false;
182+
183+
return existing.some(
184+
e =>
185+
e.description === entry.description &&
186+
e.evidenceRecorded === entry.evidenceRecorded &&
187+
e.reviewer === entry.reviewer
188+
);
189+
}
190+
public static todayDateString(): string {
191+
const now = new Date();
192+
return now.toISOString().substring(0, 10);
193+
}
194+
195+
// to be used when creating new evidence entries to ensure they have a stable UUID
196+
public static generateId(): string {
197+
return crypto.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
198+
}
199+
}

src/assets/YAML/team-evidence.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Export team evidence from the browser, and replace this file
2+
evidence:

0 commit comments

Comments
 (0)