feat(ai-act-kompass): include training register in JSON backup (schema v3)
Some checks failed
security-scan / secret-scan (push) Failing after 5s
Some checks failed
security-scan / secret-scan (push) Failing after 5s
The training register incl. scan attachments and checksums is now part of the backup export/import — the Art. 4 evidence chain survives device moves. Older backups (v1/v2) remain importable. Claude-Session: https://claude.ai/code/session_017YjohVNx1ZGNM4MX7tHpfv
This commit is contained in:
parent
dc09999472
commit
94b539ed24
@ -25,17 +25,20 @@ export function saveTraining(records: readonly TrainingRecord[]): void {
|
|||||||
/** Format der Export-/Import-Datei. */
|
/** Format der Export-/Import-Datei. */
|
||||||
export interface ExportFile {
|
export interface ExportFile {
|
||||||
readonly app: 'ai-act-kompass';
|
readonly app: 'ai-act-kompass';
|
||||||
readonly schemaVersion: 1 | 2;
|
readonly schemaVersion: 1 | 2 | 3;
|
||||||
readonly exportedAt: string;
|
readonly exportedAt: string;
|
||||||
readonly systems: readonly AISystem[];
|
readonly systems: readonly AISystem[];
|
||||||
/** Ab schemaVersion 2 enthalten. */
|
/** Ab schemaVersion 2 enthalten. */
|
||||||
readonly profile?: CompanyProfile;
|
readonly profile?: CompanyProfile;
|
||||||
|
/** Ab schemaVersion 3 enthalten: Schulungsregister inkl. Scan-Belegen. */
|
||||||
|
readonly training?: readonly TrainingRecord[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Ergebnis eines Imports (Profil optional, ältere Backups haben keins). */
|
/** Ergebnis eines Imports (ältere Backups haben kein Profil/Register). */
|
||||||
export interface ImportResult {
|
export interface ImportResult {
|
||||||
readonly systems: readonly AISystem[];
|
readonly systems: readonly AISystem[];
|
||||||
readonly profile: CompanyProfile | null;
|
readonly profile: CompanyProfile | null;
|
||||||
|
readonly training: readonly TrainingRecord[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Leeres Firmenprofil (Default). */
|
/** Leeres Firmenprofil (Default). */
|
||||||
@ -106,13 +109,15 @@ export function exportData(
|
|||||||
systems: readonly AISystem[],
|
systems: readonly AISystem[],
|
||||||
profile: CompanyProfile,
|
profile: CompanyProfile,
|
||||||
exportedAt: string,
|
exportedAt: string,
|
||||||
|
training: readonly TrainingRecord[] = [],
|
||||||
): string {
|
): string {
|
||||||
const file: ExportFile = {
|
const file: ExportFile = {
|
||||||
app: 'ai-act-kompass',
|
app: 'ai-act-kompass',
|
||||||
schemaVersion: 2,
|
schemaVersion: 3,
|
||||||
exportedAt,
|
exportedAt,
|
||||||
systems,
|
systems,
|
||||||
profile,
|
profile,
|
||||||
|
training,
|
||||||
};
|
};
|
||||||
return JSON.stringify(file, null, 2);
|
return JSON.stringify(file, null, 2);
|
||||||
}
|
}
|
||||||
@ -139,6 +144,7 @@ export function importData(json: string): ImportResult {
|
|||||||
return {
|
return {
|
||||||
systems: file.systems,
|
systems: file.systems,
|
||||||
profile: file.profile ? { ...emptyProfile(), ...file.profile } : null,
|
profile: file.profile ? { ...emptyProfile(), ...file.profile } : null,
|
||||||
|
training: Array.isArray(file.training) ? file.training : null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -211,10 +211,12 @@ export function App(): JSX.Element {
|
|||||||
<SettingsView
|
<SettingsView
|
||||||
systems={systems}
|
systems={systems}
|
||||||
profile={profile}
|
profile={profile}
|
||||||
|
training={training}
|
||||||
onProfileChange={setProfile}
|
onProfileChange={setProfile}
|
||||||
onImport={(imported, importedProfile) => {
|
onImport={(imported, importedProfile, importedTraining) => {
|
||||||
setSystems(imported);
|
setSystems(imported);
|
||||||
if (importedProfile) setProfile(importedProfile);
|
if (importedProfile) setProfile(importedProfile);
|
||||||
|
if (importedTraining) setTraining(importedTraining);
|
||||||
}}
|
}}
|
||||||
onClear={() => {
|
onClear={() => {
|
||||||
setSystems([]);
|
setSystems([]);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
|
import type { TrainingRecord } from '../engine/quiz';
|
||||||
import type { AISystem, CompanyProfile } from '../engine/types';
|
import type { AISystem, CompanyProfile } from '../engine/types';
|
||||||
import { useI18n } from '../i18n/context';
|
import { useI18n } from '../i18n/context';
|
||||||
import { LOCALE_NAMES, LOCALES } from '../i18n/types';
|
import { LOCALE_NAMES, LOCALES } from '../i18n/types';
|
||||||
@ -9,8 +10,13 @@ import { downloadText, todayISO } from './common';
|
|||||||
interface SettingsViewProps {
|
interface SettingsViewProps {
|
||||||
readonly systems: readonly AISystem[];
|
readonly systems: readonly AISystem[];
|
||||||
readonly profile: CompanyProfile;
|
readonly profile: CompanyProfile;
|
||||||
|
readonly training: readonly TrainingRecord[];
|
||||||
readonly onProfileChange: (profile: CompanyProfile) => void;
|
readonly onProfileChange: (profile: CompanyProfile) => void;
|
||||||
readonly onImport: (systems: readonly AISystem[], profile: CompanyProfile | null) => void;
|
readonly onImport: (
|
||||||
|
systems: readonly AISystem[],
|
||||||
|
profile: CompanyProfile | null,
|
||||||
|
training: readonly TrainingRecord[] | null,
|
||||||
|
) => void;
|
||||||
readonly onClear: () => void;
|
readonly onClear: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,6 +28,7 @@ type ProfileTextKey = Exclude<keyof CompanyProfile, 'logoDataUrl' | 'countries'>
|
|||||||
export function SettingsView({
|
export function SettingsView({
|
||||||
systems,
|
systems,
|
||||||
profile,
|
profile,
|
||||||
|
training,
|
||||||
onProfileChange,
|
onProfileChange,
|
||||||
onImport,
|
onImport,
|
||||||
onClear,
|
onClear,
|
||||||
@ -45,7 +52,7 @@ export function SettingsView({
|
|||||||
const handleImport = async (file: File): Promise<void> => {
|
const handleImport = async (file: File): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const result = importData(await file.text());
|
const result = importData(await file.text());
|
||||||
onImport(result.systems, result.profile);
|
onImport(result.systems, result.profile, result.training);
|
||||||
setMessage(
|
setMessage(
|
||||||
fmt(t.importOk, {
|
fmt(t.importOk, {
|
||||||
count: result.systems.length,
|
count: result.systems.length,
|
||||||
@ -190,7 +197,7 @@ export function SettingsView({
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
downloadText(
|
downloadText(
|
||||||
`ai-act-kompass-backup-${todayISO()}.json`,
|
`ai-act-kompass-backup-${todayISO()}.json`,
|
||||||
exportData(systems, profile, new Date().toISOString()),
|
exportData(systems, profile, new Date().toISOString(), training),
|
||||||
'application/json',
|
'application/json',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user