├── docs ├── .nojekyll └── index.html ├── app ├── .node-version ├── src │ ├── routes │ │ ├── +layout.ts │ │ ├── +layout.svelte │ │ └── +page.svelte │ ├── lib │ │ ├── index.ts │ │ ├── stores │ │ │ ├── language.svelte.ts │ │ │ ├── theme.svelte.ts │ │ │ └── assessment.svelte.ts │ │ ├── types │ │ │ └── gdpr.ts │ │ ├── components │ │ │ ├── ui │ │ │ │ ├── Card.svelte │ │ │ │ ├── Button.svelte │ │ │ │ └── Modal.svelte │ │ │ ├── assessment │ │ │ │ ├── CategoryCard.svelte │ │ │ │ ├── ProgressBar.svelte │ │ │ │ └── ChecklistItem.svelte │ │ │ ├── layout │ │ │ │ ├── Header.svelte │ │ │ │ └── Footer.svelte │ │ │ └── export │ │ │ │ └── ExportModal.svelte │ │ └── data │ │ │ └── gdpr-data.ts │ ├── app.d.ts │ ├── app.html │ └── app.css ├── static │ ├── robots.txt │ └── favicon.svg ├── .npmrc ├── vercel.json ├── vite.config.ts ├── .gitignore ├── svelte.config.js ├── tsconfig.json ├── package.json └── README.md ├── .gitignore ├── LICENSE ├── .github └── workflows │ ├── deploy-pages.yml │ └── README.md ├── CONTRIBUTING.md ├── README.md └── webapp ├── README.md ├── js ├── overview.js ├── main.js ├── assessment.js └── export.js └── index.html /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/.node-version: -------------------------------------------------------------------------------- 1 | 22 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | DPF_UPDATE_SUMMARY.md 4 | -------------------------------------------------------------------------------- /app/src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | export const prerender = true; 2 | export const ssr = false; 3 | -------------------------------------------------------------------------------- /app/static/robots.txt: -------------------------------------------------------------------------------- 1 | # allow crawling everything by default 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /app/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // place files you want to import through the `$lib` alias in this folder. 2 | -------------------------------------------------------------------------------- /app/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | onlyBuiltDependencies[]=esbuild 3 | onlyBuiltDependencies[]=core-js 4 | -------------------------------------------------------------------------------- /app/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | {@render children?.()} 9 | -------------------------------------------------------------------------------- /app/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildCommand": "pnpm run build", 3 | "devCommand": "pnpm run dev", 4 | "installCommand": "pnpm install", 5 | "framework": "sveltekit" 6 | } 7 | -------------------------------------------------------------------------------- /app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Output 4 | .output 5 | .vercel 6 | .netlify 7 | .wrangler 8 | /.svelte-kit 9 | /build 10 | 11 | # OS 12 | .DS_Store 13 | Thumbs.db 14 | 15 | # Env 16 | .env 17 | .env.* 18 | !.env.example 19 | !.env.test 20 | 21 | # Vite 22 | vite.config.js.timestamp-* 23 | vite.config.ts.timestamp-* 24 | -------------------------------------------------------------------------------- /app/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://svelte.dev/docs/kit/types#app.d.ts 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /app/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /app/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-vercel'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://svelte.dev/docs/kit/integrations 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // Vercel adapter for deployment 12 | // See https://vercel.com/docs/frameworks/sveltekit 13 | adapter: adapter({ 14 | runtime: 'nodejs22.x' 15 | }), 16 | alias: { 17 | $lib: './src/lib' 18 | } 19 | } 20 | }; 21 | 22 | export default config; 23 | -------------------------------------------------------------------------------- /app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias 15 | // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files 16 | // 17 | // To make changes to top-level options such as include and exclude, we recommend extending 18 | // the generated config; see https://svelte.dev/docs/kit/configuration#typescript 19 | } 20 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gdpr-svelte", 3 | "private": true, 4 | "version": "0.0.1", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite dev", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "prepare": "svelte-kit sync || echo ''", 11 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 12 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" 13 | }, 14 | "devDependencies": { 15 | "@sveltejs/adapter-vercel": "^6.1.1", 16 | "@sveltejs/kit": "^2.43.2", 17 | "@sveltejs/vite-plugin-svelte": "^6.2.0", 18 | "svelte": "^5.39.5", 19 | "svelte-check": "^4.3.2", 20 | "typescript": "^5.9.2", 21 | "vite": "^7.1.7" 22 | }, 23 | "dependencies": { 24 | "@fontsource/inter": "^5.2.8", 25 | "jspdf": "^3.0.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mirko Schubert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/src/lib/stores/language.svelte.ts: -------------------------------------------------------------------------------- 1 | import type { Language } from '$lib/types/gdpr'; 2 | 3 | class LanguageState { 4 | current = $state('en'); 5 | 6 | constructor() { 7 | if (typeof window !== 'undefined') { 8 | this.loadFromStorage(); 9 | } 10 | } 11 | 12 | toggle() { 13 | this.current = this.current === 'en' ? 'de' : 'en'; 14 | this.saveToStorage(); 15 | } 16 | 17 | setLanguage(lang: Language) { 18 | this.current = lang; 19 | this.saveToStorage(); 20 | } 21 | 22 | private saveToStorage() { 23 | if (typeof localStorage === 'undefined') return; 24 | localStorage.setItem('gdpr_language', this.current); 25 | } 26 | 27 | private loadFromStorage() { 28 | if (typeof localStorage === 'undefined') return; 29 | 30 | const savedLang = localStorage.getItem('gdpr_language'); 31 | if (savedLang === 'en' || savedLang === 'de') { 32 | this.current = savedLang; 33 | } else { 34 | // Detect browser language 35 | const browserLang = navigator.language.toLowerCase(); 36 | if (browserLang.startsWith('de')) { 37 | this.current = 'de'; 38 | } 39 | } 40 | } 41 | } 42 | 43 | export const language = new LanguageState(); 44 | -------------------------------------------------------------------------------- /app/src/lib/stores/theme.svelte.ts: -------------------------------------------------------------------------------- 1 | export type Theme = 'light' | 'dark'; 2 | 3 | class ThemeState { 4 | current = $state('light'); 5 | 6 | constructor() { 7 | if (typeof window !== 'undefined') { 8 | this.loadFromStorage(); 9 | this.applyTheme(); 10 | } 11 | } 12 | 13 | toggle() { 14 | this.current = this.current === 'light' ? 'dark' : 'light'; 15 | this.applyTheme(); 16 | this.saveToStorage(); 17 | } 18 | 19 | setTheme(theme: Theme) { 20 | this.current = theme; 21 | this.applyTheme(); 22 | this.saveToStorage(); 23 | } 24 | 25 | private applyTheme() { 26 | if (typeof document === 'undefined') return; 27 | document.documentElement.setAttribute('data-theme', this.current); 28 | } 29 | 30 | private saveToStorage() { 31 | if (typeof localStorage === 'undefined') return; 32 | localStorage.setItem('gdpr_theme', this.current); 33 | } 34 | 35 | private loadFromStorage() { 36 | if (typeof localStorage === 'undefined') return; 37 | 38 | const savedTheme = localStorage.getItem('gdpr_theme'); 39 | if (savedTheme === 'light' || savedTheme === 'dark') { 40 | this.current = savedTheme; 41 | } else { 42 | // Check system preference 43 | if (window.matchMedia('(prefers-color-scheme: dark)').matches) { 44 | this.current = 'dark'; 45 | } 46 | } 47 | } 48 | } 49 | 50 | export const theme = new ThemeState(); 51 | -------------------------------------------------------------------------------- /.github/workflows/deploy-pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | concurrency: 14 | group: "pages" 15 | cancel-in-progress: false 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout repository 22 | uses: actions/checkout@v4 23 | 24 | - name: Setup Pages 25 | uses: actions/configure-pages@v4 26 | 27 | - name: Build site structure 28 | run: | 29 | # Create deployment directory 30 | mkdir -p _site 31 | 32 | # Copy docs content (main site) 33 | cp -r docs/* _site/ 34 | 35 | # Copy webapp into the site 36 | cp -r webapp _site/webapp 37 | 38 | # List structure for verification 39 | echo "Site structure:" 40 | ls -la _site/ 41 | echo "Webapp structure:" 42 | ls -la _site/webapp/ 43 | 44 | - name: Upload artifact 45 | uses: actions/upload-pages-artifact@v3 46 | with: 47 | path: ./_site 48 | 49 | deploy: 50 | environment: 51 | name: github-pages 52 | url: ${{ steps.deployment.outputs.page_url }} 53 | runs-on: ubuntu-latest 54 | needs: build 55 | steps: 56 | - name: Deploy to GitHub Pages 57 | id: deployment 58 | uses: actions/deploy-pages@v4 59 | -------------------------------------------------------------------------------- /app/static/favicon.svg: -------------------------------------------------------------------------------- 1 | svelte-logo -------------------------------------------------------------------------------- /app/src/lib/types/gdpr.ts: -------------------------------------------------------------------------------- 1 | export interface ChecklistItem { 2 | id: string; 3 | title: string; 4 | description: string; 5 | required: boolean; 6 | weight: number; 7 | legalBasis: string[]; 8 | implementation: string[]; 9 | explanation?: string[]; 10 | legitimationReason?: string[]; 11 | } 12 | 13 | export interface Category { 14 | id: string; 15 | title: string; 16 | icon: string; 17 | description: string; 18 | items: ChecklistItem[]; 19 | } 20 | 21 | export interface GDPRData { 22 | version?: string; 23 | lastUpdated?: string; 24 | title?: string; 25 | subtitle?: string; 26 | categories: Category[]; 27 | } 28 | 29 | export interface AssessmentResponse { 30 | [itemId: string]: boolean; 31 | } 32 | 33 | export interface ScoringConfig { 34 | weights: { 35 | required: number; 36 | optional: number; 37 | }; 38 | thresholds: { 39 | excellent: number; 40 | good: number; 41 | moderate: number; 42 | poor: number; 43 | }; 44 | labels: { 45 | [lang: string]: { 46 | [level: string]: string; 47 | }; 48 | }; 49 | descriptions: { 50 | [lang: string]: { 51 | [level: string]: string; 52 | }; 53 | }; 54 | } 55 | 56 | export interface AssessmentExport { 57 | version: string; 58 | timestamp: string; 59 | language: Language; 60 | responses: AssessmentResponse; 61 | ignoredItems: string[]; 62 | scores: { 63 | completed: number; 64 | total: number; 65 | currentScore: number; 66 | maxScore: number; 67 | percentage: number; 68 | }; 69 | } 70 | 71 | export type Language = 'en' | 'de'; 72 | export type Theme = 'light' | 'dark'; 73 | export type NavigationSection = 'assessment' | 'overview' | 'export'; 74 | export type ComplianceLevel = 'excellent' | 'good' | 'moderate' | 'poor' | 'critical'; 75 | -------------------------------------------------------------------------------- /app/src/lib/components/ui/Card.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 |
{ 29 | if (e.key === 'Enter' || e.key === ' ') { 30 | e.preventDefault(); 31 | onclick?.(); 32 | } 33 | } 34 | : undefined} 35 | role={interactive ? 'button' : undefined} 36 | tabindex={interactive ? 0 : undefined} 37 | > 38 | {#if title} 39 |

{title}

40 | {/if} 41 | {@render children()} 42 |
43 | 44 | 91 | -------------------------------------------------------------------------------- /app/src/lib/data/gdpr-data.ts: -------------------------------------------------------------------------------- 1 | import type { GDPRData, ScoringConfig } from '$lib/types/gdpr'; 2 | import enData from './en.json'; 3 | import deData from './de.json'; 4 | 5 | export const gdprData: Record<'en' | 'de', GDPRData> = { 6 | en: enData as GDPRData, 7 | de: deData as GDPRData 8 | }; 9 | 10 | export const scoringConfig: ScoringConfig = { 11 | weights: { 12 | required: 1.0, 13 | optional: 0.5 14 | }, 15 | thresholds: { 16 | excellent: 90, 17 | good: 75, 18 | moderate: 60, 19 | poor: 40 20 | }, 21 | labels: { 22 | en: { 23 | excellent: 'Excellent Compliance', 24 | good: 'Good Compliance', 25 | moderate: 'Moderate Compliance', 26 | poor: 'Poor Compliance', 27 | critical: 'Critical Issues' 28 | }, 29 | de: { 30 | excellent: 'Ausgezeichnete Konformität', 31 | good: 'Gute Konformität', 32 | moderate: 'Moderate Konformität', 33 | poor: 'Schlechte Konformität', 34 | critical: 'Kritische Probleme' 35 | } 36 | }, 37 | descriptions: { 38 | en: { 39 | excellent: 'Your website demonstrates excellent GDPR compliance across all categories.', 40 | good: 'Your website shows good GDPR compliance with minor areas for improvement.', 41 | moderate: 'Your website has moderate compliance but requires attention in several areas.', 42 | poor: 'Your website has significant compliance gaps that need immediate attention.', 43 | critical: 'Your website has critical compliance issues requiring urgent action.' 44 | }, 45 | de: { 46 | excellent: 'Ihre Website zeigt ausgezeichnete DSGVO-Konformität in allen Bereichen.', 47 | good: 'Ihre Website zeigt gute DSGVO-Konformität mit geringfügigen Verbesserungsmöglichkeiten.', 48 | moderate: 49 | 'Ihre Website hat moderate Konformität, benötigt aber Aufmerksamkeit in mehreren Bereichen.', 50 | poor: 'Ihre Website hat erhebliche Konformitätslücken, die sofortige Aufmerksamkeit erfordern.', 51 | critical: 'Ihre Website hat kritische Konformitätsprobleme, die dringendes Handeln erfordern.' 52 | } 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /app/src/lib/components/ui/Button.svelte: -------------------------------------------------------------------------------- 1 | 24 | 25 | 33 | 34 | 112 | -------------------------------------------------------------------------------- /app/src/lib/components/assessment/CategoryCard.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 | 28 | 48 | 49 |
50 |
51 |
52 | 53 | {#if isExpanded} 54 |
55 | {#each category.items as item (item.id)} 56 | 57 | {/each} 58 |
59 | {/if} 60 |
61 | 62 | 135 | -------------------------------------------------------------------------------- /.github/workflows/README.md: -------------------------------------------------------------------------------- 1 | # GitHub Pages Deployment Workflow 2 | 3 | This GitHub Actions workflow automatically deploys the GDPR Compliance Checker to GitHub Pages. 4 | 5 | ## What it does 6 | 7 | 1. **Triggers on:** 8 | - Push to `master` branch 9 | - Manual workflow dispatch (can be run manually from GitHub Actions tab) 10 | 11 | 2. **Build Process:** 12 | - Creates a `_site` directory for deployment 13 | - Copies all content from `docs/` (main documentation site) 14 | - Copies `webapp/` folder into `_site/webapp/` (interactive tool) 15 | - Uploads the combined structure as a Pages artifact 16 | 17 | 3. **Deploy Process:** 18 | - Deploys the artifact to GitHub Pages 19 | - Makes the site available at: `https://pankajydv07.github.io/datenschutz-checkliste/` 20 | 21 | ## Site Structure After Deployment 22 | 23 | ``` 24 | https://pankajydv07.github.io/datenschutz-checkliste/ 25 | ├── index.html (main docs site) 26 | ├── webapp/ 27 | │ ├── index.html (interactive tool) 28 | │ ├── js/ 29 | │ ├── styles/ 30 | │ └── ... 31 | └── ... 32 | ``` 33 | 34 | ## How to Use 35 | 36 | ### Automatic Deployment 37 | Simply push your changes to the `master` branch: 38 | ```bash 39 | git add . 40 | git commit -m "Your commit message" 41 | git push origin master 42 | ``` 43 | 44 | The workflow will automatically run and deploy your changes within 2-3 minutes. 45 | 46 | ### Manual Deployment 47 | 1. Go to the repository on GitHub 48 | 2. Click "Actions" tab 49 | 3. Select "Deploy to GitHub Pages" workflow 50 | 4. Click "Run workflow" button 51 | 5. Select the `master` branch 52 | 6. Click "Run workflow" 53 | 54 | ## Permissions Required 55 | 56 | The workflow uses the following permissions: 57 | - `contents: read` - To read repository content 58 | - `pages: write` - To deploy to GitHub Pages 59 | - `id-token: write` - For authentication 60 | 61 | ## Troubleshooting 62 | 63 | ### Workflow fails with "permission denied" 64 | 1. Go to Settings → Actions → General 65 | 2. Under "Workflow permissions", ensure: 66 | - "Read and write permissions" is selected 67 | - "Allow GitHub Actions to create and approve pull requests" is checked 68 | 69 | ### 404 Error on webapp 70 | 1. Check the workflow run logs 71 | 2. Verify that `webapp/` folder exists in the repository 72 | 3. Ensure the link in `docs/index.html` points to `webapp/index.html` (not `../webapp/index.html`) 73 | 74 | ### Changes not reflecting on site 75 | 1. Wait 2-3 minutes after workflow completes 76 | 2. Hard refresh your browser (Ctrl+Shift+R or Cmd+Shift+R) 77 | 3. Clear browser cache if needed 78 | 79 | ## Monitoring 80 | 81 | Check deployment status: 82 | - **Actions tab:** See real-time workflow execution 83 | - **Environments:** View deployment history under repository Settings → Environments → github-pages 84 | - **Pages settings:** Settings → Pages shows the current deployment status 85 | 86 | ## Local Testing 87 | 88 | To test the site structure locally before deploying: 89 | ```bash 90 | # Create the same structure 91 | mkdir -p _site 92 | cp -r docs/* _site/ 93 | cp -r webapp _site/webapp 94 | 95 | # Serve locally (requires Python) 96 | cd _site 97 | python -m http.server 8080 98 | 99 | # Open http://localhost:8080 in your browser 100 | ``` 101 | 102 | ## Notes 103 | 104 | - The workflow preserves both the documentation site and interactive tool 105 | - No manual file copying required - everything is automated 106 | - Changes to either `docs/` or `webapp/` trigger a full rebuild 107 | - Old deployments are automatically replaced 108 | -------------------------------------------------------------------------------- /app/src/lib/components/ui/Modal.svelte: -------------------------------------------------------------------------------- 1 | 27 | 28 | {#if isOpen} 29 | 59 | {/if} 60 | 61 | 161 | -------------------------------------------------------------------------------- /app/src/lib/components/layout/Header.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 |
18 |
19 | 34 | 35 | 78 |
79 |
80 |
81 | 82 | 144 | -------------------------------------------------------------------------------- /app/src/lib/components/layout/Footer.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 | 47 | 48 | 54 |
55 |
56 | 57 | 170 | -------------------------------------------------------------------------------- /app/src/lib/components/assessment/ProgressBar.svelte: -------------------------------------------------------------------------------- 1 | 30 | 31 |
32 |
33 |
34 |

Overall Progress

35 | {#if showDetails} 36 |

37 | {assessment.completedItems} of {assessment.totalItems} items completed 38 |

39 | {/if} 40 |
41 |
{Math.round(progress)}%
42 |
43 | 44 |
45 |
46 |
47 | 48 | {#if showDetails} 49 |
50 |
51 | {levelLabels[level]} Compliance 52 |
53 |
54 | Score: {Math.round(score)}% ({Math.round(assessment.currentScore)}/{Math.round( 55 | assessment.maxScore 56 | )} points) 57 |
58 |
59 | {/if} 60 |
61 | 62 | 167 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | # GDPR Compliance Checker - Svelte 5 Edition 2 | 3 | A modern, interactive web application for assessing and improving GDPR (General Data Protection Regulation) compliance. Built with **Svelte 5** using the new Runes API for superior reactivity and performance. 4 | 5 | ## ✨ Features 6 | 7 | - 📋 **Comprehensive Checklist**: 9 GDPR compliance categories with detailed requirements 8 | - 🎯 **Progress Tracking**: Real-time assessment progress and compliance scoring 9 | - 💾 **Data Persistence**: Automatic save to local storage 10 | - 📤 **Export/Import**: Save assessments as JSON or generate PDF reports 11 | - 🌐 **Multilingual**: Full support for English and German 12 | - 🌓 **Dark Mode**: Beautiful light and dark themes 13 | - ♿ **Accessible**: WCAG compliant with keyboard navigation 14 | - 📱 **Responsive**: Works perfectly on all devices 15 | - ⚡ **Fast**: Built with Svelte 5 for optimal performance 16 | 17 | ## 🚀 Getting Started 18 | 19 | ### Prerequisites 20 | 21 | - Node.js 18+ and npm 22 | 23 | ### Installation 24 | 25 | ```bash 26 | # Install dependencies 27 | npm install 28 | 29 | # Start development server 30 | npm run dev 31 | 32 | # Build for production 33 | npm run build 34 | 35 | # Preview production build 36 | npm run preview 37 | ``` 38 | 39 | ## 📁 Project Structure 40 | 41 | ``` 42 | gdpr_svelte/ 43 | ├── src/ 44 | │ ├── lib/ 45 | │ │ ├── components/ 46 | │ │ │ ├── assessment/ # Assessment-related components 47 | │ │ │ ├── export/ # Export/Import functionality 48 | │ │ │ ├── layout/ # Layout components 49 | │ │ │ └── ui/ # Reusable UI primitives 50 | │ │ ├── data/ 51 | │ │ │ ├── gdpr-data.ts # Data module 52 | │ │ │ ├── en.json # English GDPR data 53 | │ │ │ └── de.json # German GDPR data 54 | │ │ ├── stores/ # Svelte 5 Runes stores 55 | │ │ │ ├── assessment.svelte.ts 56 | │ │ │ ├── theme.svelte.ts 57 | │ │ │ └── language.svelte.ts 58 | │ │ └── types/ 59 | │ │ └── gdpr.ts # TypeScript type definitions 60 | │ ├── routes/ 61 | │ │ ├── +layout.svelte # Root layout 62 | │ │ ├── +layout.ts # Layout config 63 | │ │ └── +page.svelte # Main page 64 | │ └── app.css # Global styles 65 | ├── package.json 66 | ├── svelte.config.js 67 | └── README.md 68 | ``` 69 | 70 | ## 🛠️ Technologies 71 | 72 | - **Svelte 5**: Latest version with Runes API for reactive state 73 | - **SvelteKit**: Full-stack framework with static adapter 74 | - **TypeScript**: Type-safe development 75 | - **Vite**: Lightning-fast build tool 76 | - **jsPDF**: PDF generation 77 | - **Inter Font**: Modern, accessible typography 78 | 79 | ## 📊 GDPR Compliance Categories 80 | 81 | 1. Legal Basis and Consent Management 82 | 2. Data Mapping and Inventory 83 | 3. Data Subject Rights (DSR) 84 | 4. Data Protection Impact Assessment (DPIA) 85 | 5. Security and Breach Management 86 | 6. Data Processing Agreements 87 | 7. Privacy Notices and Transparency 88 | 8. Data Retention and Deletion 89 | 9. International Data Transfers 90 | 91 | ## 🎨 Key Features 92 | 93 | ### Assessment System 94 | 95 | - Interactive checklist with expandable categories 96 | - Real-time progress tracking 97 | - Weighted scoring (required vs optional items) 98 | - Compliance level indicators 99 | - Priority recommendations 100 | 101 | ### Data Management 102 | 103 | - Auto-save to local storage 104 | - JSON export/import 105 | - PDF report generation 106 | - Reset functionality 107 | 108 | ### UI/UX 109 | 110 | - Modern, clean design 111 | - Smooth animations 112 | - Dark/light theme toggle 113 | - Responsive layout 114 | - Keyboard accessible 115 | 116 | ## 🌐 Deployment 117 | 118 | Build output is a static site that can be deployed to: 119 | 120 | - GitHub Pages 121 | - Netlify 122 | - Vercel 123 | - Any static hosting 124 | 125 | Build command: `npm run build` 126 | Output directory: `build/` 127 | 128 | ## ⚖️ Legal Notice 129 | 130 | This tool provides guidance only. For legal compliance advice, consult a qualified legal professional. 131 | 132 | --- 133 | 134 | **Built with ❤️ using Svelte 5** 135 | -------------------------------------------------------------------------------- /app/src/app.css: -------------------------------------------------------------------------------- 1 | @import '@fontsource/inter/latin.css'; 2 | 3 | :root { 4 | /* Light Theme Colors */ 5 | --color-primary: #2563eb; 6 | --color-primary-dark: #1e40af; 7 | --color-success: #10b981; 8 | --color-danger: #ef4444; 9 | --color-danger-dark: #dc2626; 10 | --color-warning: #f59e0b; 11 | 12 | --color-bg: #f8fafc; 13 | --color-bg-secondary: #f1f5f9; 14 | --color-bg-tertiary: #e2e8f0; 15 | --color-bg-translucent: rgba(248, 250, 252, 0.8); 16 | 17 | --color-card-bg: #ffffff; 18 | 19 | --color-text: #0f172a; 20 | --color-text-secondary: #64748b; 21 | 22 | --color-border: #e2e8f0; 23 | 24 | --color-success-light: #d1fae5; 25 | --color-danger-light: #fee2e2; 26 | 27 | /* Shadows */ 28 | --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); 29 | --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); 30 | --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); 31 | 32 | /* Font */ 33 | --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 34 | 'Helvetica Neue', Arial, sans-serif; 35 | } 36 | 37 | [data-theme='dark'] { 38 | /* Dark Theme Colors */ 39 | --color-bg: #0f172a; 40 | --color-bg-secondary: #1e293b; 41 | --color-bg-tertiary: #334155; 42 | --color-bg-translucent: rgba(15, 23, 42, 0.8); 43 | 44 | --color-card-bg: #1e293b; 45 | 46 | --color-text: #f1f5f9; 47 | --color-text-secondary: #94a3b8; 48 | 49 | --color-border: #334155; 50 | 51 | --color-success-light: #064e3b; 52 | --color-danger-light: #7f1d1d; 53 | 54 | /* Adjust shadows for dark mode */ 55 | --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3); 56 | --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3); 57 | --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -2px rgba(0, 0, 0, 0.4); 58 | } 59 | 60 | * { 61 | box-sizing: border-box; 62 | } 63 | 64 | html { 65 | font-family: var(--font-sans); 66 | -webkit-font-smoothing: antialiased; 67 | -moz-osx-font-smoothing: grayscale; 68 | } 69 | 70 | body { 71 | margin: 0; 72 | padding: 0; 73 | background: var(--color-bg); 74 | color: var(--color-text); 75 | line-height: 1.6; 76 | transition: background-color 0.3s ease, color 0.3s ease; 77 | } 78 | 79 | /* Smooth scrolling */ 80 | html { 81 | scroll-behavior: smooth; 82 | } 83 | 84 | /* Selection */ 85 | ::selection { 86 | background-color: var(--color-primary); 87 | color: white; 88 | } 89 | 90 | /* Focus styles */ 91 | :focus-visible { 92 | outline: 2px solid var(--color-primary); 93 | outline-offset: 2px; 94 | } 95 | 96 | /* Custom scrollbar */ 97 | ::-webkit-scrollbar { 98 | width: 10px; 99 | height: 10px; 100 | } 101 | 102 | ::-webkit-scrollbar-track { 103 | background: var(--color-bg-secondary); 104 | } 105 | 106 | ::-webkit-scrollbar-thumb { 107 | background: var(--color-border); 108 | border-radius: 5px; 109 | } 110 | 111 | ::-webkit-scrollbar-thumb:hover { 112 | background: var(--color-text-secondary); 113 | } 114 | 115 | /* Typography */ 116 | h1, 117 | h2, 118 | h3, 119 | h4, 120 | h5, 121 | h6 { 122 | margin: 0; 123 | line-height: 1.2; 124 | font-weight: 700; 125 | } 126 | 127 | p { 128 | margin: 0; 129 | } 130 | 131 | a { 132 | color: var(--color-primary); 133 | text-decoration: none; 134 | } 135 | 136 | a:hover { 137 | text-decoration: underline; 138 | } 139 | 140 | /* Utilities */ 141 | .sr-only { 142 | position: absolute; 143 | width: 1px; 144 | height: 1px; 145 | padding: 0; 146 | margin: -1px; 147 | overflow: hidden; 148 | clip: rect(0, 0, 0, 0); 149 | white-space: nowrap; 150 | border-width: 0; 151 | } 152 | 153 | /* Animations */ 154 | @keyframes fadeIn { 155 | from { 156 | opacity: 0; 157 | } 158 | to { 159 | opacity: 1; 160 | } 161 | } 162 | 163 | @keyframes slideUp { 164 | from { 165 | transform: translateY(20px); 166 | opacity: 0; 167 | } 168 | to { 169 | transform: translateY(0); 170 | opacity: 1; 171 | } 172 | } 173 | 174 | @keyframes slideDown { 175 | from { 176 | transform: translateY(-20px); 177 | opacity: 0; 178 | } 179 | to { 180 | transform: translateY(0); 181 | opacity: 1; 182 | } 183 | } 184 | 185 | @keyframes pulse { 186 | 0%, 187 | 100% { 188 | opacity: 1; 189 | } 190 | 50% { 191 | opacity: 0.5; 192 | } 193 | } 194 | 195 | /* Print styles */ 196 | @media print { 197 | body { 198 | background: white; 199 | color: black; 200 | } 201 | 202 | .no-print { 203 | display: none !important; 204 | } 205 | 206 | a { 207 | text-decoration: underline; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Data Protection Checklist 6 | 7 | 8 | 9 | 10 | 11 | 12 | 75 | 76 | 77 | 90 |
91 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /app/src/lib/components/export/ExportModal.svelte: -------------------------------------------------------------------------------- 1 | 127 | 128 | 129 |
130 |

Export Assessment

131 |

Download your assessment progress to save or share.

132 | 133 |
134 | 146 | 147 | 158 |
159 |
160 | 161 |
162 | 163 |
164 |

Import Assessment

165 |

Load a previously saved assessment from a JSON file.

166 | 167 | 174 | 175 | 187 |
188 |
189 | 190 | 221 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Data Protection Checklist 2 | 3 | We welcome contributions to the Data Protection Checklist! This project participates in **Hacktoberfest 2025** and we're excited to collaborate with developers from around the world. 4 | 5 | ## 🎯 Project Overview 6 | 7 | This is an open source checklist helping web designers and developers implement privacy-related tasks according to current data protection laws (GDPR/DSGVO). The project includes: 8 | 9 | - **English version**: `cheat-sheet.md` 10 | - **German version**: `checkliste.md` 11 | - **Documentation website**: Built with Docsify, hosted on GitHub Pages 12 | 13 | ## 🚀 How to Contribute 14 | 15 | ### Types of Contributions We Welcome 16 | 17 | 1. **Content Updates** 📝 18 | - Add new data protection requirements 19 | - Update existing legal references 20 | - Improve implementation advice 21 | - Fix typos or grammar issues 22 | 23 | 2. **Legal Compliance** ⚖️ 24 | - Update for new GDPR regulations 25 | - Add country-specific requirements 26 | - Review and verify legal accuracy 27 | 28 | 3. **Technical Improvements** 🔧 29 | - Website enhancements (Docsify configuration) 30 | - Accessibility improvements 31 | - Mobile responsiveness 32 | - Performance optimizations 33 | 34 | 4. **Translations** 🌍 35 | - Add new language versions 36 | - Improve existing translations 37 | - Localize legal requirements for different countries 38 | 39 | 5. **Documentation** 📚 40 | - Improve README 41 | - Add code comments 42 | - Create usage examples 43 | 44 | ### Getting Started 45 | 46 | 1. **Fork the repository** 47 | ```bash 48 | # Click the "Fork" button on GitHub 49 | ``` 50 | 51 | 2. **Clone your fork** 52 | ```bash 53 | git clone https://github.com/YOUR-USERNAME/datenschutz-checkliste.git 54 | cd datenschutz-checkliste 55 | ``` 56 | 57 | 3. **Create a feature branch** 58 | ```bash 59 | git checkout -b feature/your-improvement 60 | ``` 61 | 62 | 4. **Make your changes** 63 | - Edit the relevant files 64 | - Test your changes locally 65 | - Ensure proper formatting 66 | 67 | 5. **Commit your changes** 68 | ```bash 69 | git add . 70 | git commit -m "Add: your descriptive commit message" 71 | ``` 72 | 73 | 6. **Push to your fork** 74 | ```bash 75 | git push origin feature/your-improvement 76 | ``` 77 | 78 | 7. **Create a Pull Request** 79 | - Go to the original repository 80 | - Click "New Pull Request" 81 | - Provide a clear description of your changes 82 | 83 | ## 📋 Contribution Guidelines 84 | 85 | ### Content Guidelines 86 | 87 | - **Accuracy First**: All legal information must be accurate and up-to-date 88 | - **Clear Language**: Use simple, understandable language 89 | - **Implementation Focus**: Provide practical, actionable advice 90 | - **Source References**: Include links to official legal sources 91 | - **Bilingual**: Major updates should be reflected in both languages 92 | 93 | ### Technical Guidelines 94 | 95 | - **Markdown Format**: Use proper Markdown formatting 96 | - **Consistent Style**: Follow existing code style and structure 97 | - **Test Locally**: Test documentation website locally before submitting 98 | - **Mobile First**: Ensure changes work on mobile devices 99 | 100 | ### Code Style 101 | 102 | - Use consistent formatting for Markdown files 103 | - Keep lines under 120 characters when possible 104 | - Use descriptive commit messages 105 | - Add comments for complex configurations 106 | 107 | ## 🛠️ Local Development 108 | 109 | ### Testing the Documentation Website 110 | 111 | 1. **Serve locally** (optional, for testing Docsify changes): 112 | ```bash 113 | # Install docsify-cli globally 114 | npm i docsify-cli -g 115 | 116 | # Serve the docs 117 | docsify serve docs 118 | ``` 119 | 120 | 2. **View in browser**: http://localhost:3000 121 | 122 | ### File Structure 123 | 124 | ``` 125 | ├── cheat-sheet.md # English checklist 126 | ├── checkliste.md # German checklist 127 | ├── docs/ 128 | │ ├── index.html # Docsify configuration 129 | │ └── .nojekyll # GitHub Pages config 130 | ├── README.md # Project overview 131 | └── CONTRIBUTING.md # This file 132 | ``` 133 | 134 | ## 🏷️ Hacktoberfest 2025 135 | 136 | This project proudly participates in Hacktoberfest! To ensure your contribution counts: 137 | 138 | 1. **Quality over Quantity**: Focus on meaningful contributions 139 | 2. **Follow Guidelines**: Adhere to these contribution guidelines 140 | 3. **Be Respectful**: Follow our Code of Conduct 141 | 4. **Valid PRs Only**: Spam or low-quality PRs will be marked as invalid 142 | 143 | ### Hacktoberfest Labels 144 | 145 | We use these labels for Hacktoberfest: 146 | - `hacktoberfest` - General Hacktoberfest issues 147 | - `good-first-issue` - Perfect for newcomers 148 | - `help-wanted` - We need your expertise 149 | - `documentation` - Documentation improvements 150 | - `translation` - Translation work needed 151 | 152 | ## 🤝 Code of Conduct 153 | 154 | We are committed to providing a welcoming and inclusive environment for all contributors. Please: 155 | 156 | - Be respectful and considerate 157 | - Use welcoming and inclusive language 158 | - Focus on what is best for the community 159 | - Show empathy towards other community members 160 | - Respect differing viewpoints and experiences 161 | 162 | ## 📞 Getting Help 163 | 164 | Need help or have questions? 165 | 166 | - **Issues**: Create a GitHub issue for bugs or feature requests 167 | - **Discussions**: Use GitHub Discussions for general questions 168 | - **Email**: Contact the maintainer for sensitive topics 169 | 170 | ## 🎉 Recognition 171 | 172 | Contributors will be: 173 | - Listed in the project's contributor list 174 | - Mentioned in release notes for significant contributions 175 | - Eligible for Hacktoberfest rewards (if participating) 176 | 177 | ## 📄 Legal Note 178 | 179 | By contributing to this project, you agree that your contributions will be licensed under the same license as the project. This project provides general information and does not constitute legal advice. 180 | 181 | --- 182 | 183 | **Happy Contributing! 🚀** 184 | 185 | Thank you for helping make data protection more accessible for developers worldwide! -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Data Protection Cheat Sheet (GDPR)

2 | 3 |

4 | 5 | Hacktoberfest 2025 6 | 7 |
8 | Deploy Status 9 | Languages 10 | 11 | License 12 | 13 | Stars 14 | Forks 15 | Contributors 16 |

17 | 18 |

Open source cheat sheet for web designers and developers to implement data protection relevant tasks according to current law (GDPR).

19 | 20 |

Buy Me A Coffee

21 | 22 |

Initiated by Peter Haurand and Mirko Schubert

23 | 24 | ## 🚀 Interactive Web App Available! 25 | 26 | **Experience GDPR compliance assessment like never before:** 27 | 28 |

29 | 30 | Launch Interactive Tool 31 | 32 |

33 | 34 | ### ✨ Features 35 | - **⚡ Real-time Assessment** - Interactive checklists with instant scoring 36 | - **📊 Visual Reports** - Professional compliance reports with category breakdowns 37 | - **💾 Export & Share** - Save assessments as JSON or generate PDF reports 38 | - **🌍 Multilingual** - Complete support for English and German 39 | - **📱 Responsive Design** - Works perfectly on all devices 40 | - **🎯 Extensible** - Ready for AI Act, accessibility, and copyright compliance 41 | 42 | --- 43 | 44 | ## TL;DR 45 | 46 | **Quick Start**: Ready-to-use GDPR checklists for developers. **View Interactive Documentation** • [🇩🇪 Deutsche Checkliste](checkliste.md) • [🇺🇸 English Cheat Sheet](cheat-sheet.md) 47 | 48 |

49 | 50 | Visit Documentation Website 51 | 52 |

53 | 54 | ## 🎃 Hacktoberfest 2025 55 | 56 | **We're participating in Hacktoberfest 2025!** 🎉 Help us improve data protection resources for developers worldwide. 57 | 58 | - **Contribute**: Legal updates, translations, technical improvements 59 | - **Get Started**: Check our [Contributing Guidelines](CONTRIBUTING.md) 60 | - **Find Issues**: Look for `good-first-issue` and `hacktoberfest` labels 61 | - **All Levels Welcome**: From beginners to experts 62 | 63 |

64 | 65 | Join Hacktoberfest 66 | 67 |

68 | 69 | --- 70 | 71 | This **Data Protection Cheat Sheet** is intended to serve as a general orientation for web designers and developers as well as, of course, for every interested website operator in order to prepare websites and commissioned works in accordance with currently valid German and European law. 72 | 73 | Peter and I have taken the knowledge we have gathered over the past few months on the occasion of the GDPR and developed this list. It is still "work-in-progress" - currently I am, for example, searching for the relevant legal bases, judgments and statements / articles of lawyers. We also invite everyone to [contribute](https://github.com/mirkoschubert/datenschutz-checkliste#du-möchtest-gerne-mitwirken) via this Github repository . 74 | 75 | ### Disclaimer 76 | 77 | The **Data Protection Checklist** cannot address individual cases and does **not constitute legal advice**, but is merely a collection of knowledge that has been compiled from extensive research, our own experience and "best practices" of various developers and is constantly being further developed. We assume **no liability** and recommend to consult a lawyer for legal questions in any case. 78 | 79 | ## How to Contribute 80 | 81 | This repository thrives on community contributions! We welcome developers, legal experts, and privacy enthusiasts to help improve these resources. 82 | 83 | ### Quick Contribution Options 84 | 85 | * **Content**: Update legal requirements, add implementation tips, fix typos 86 | * **Translation**: Help translate checklists to new languages 87 | * **Technical**: Improve website, accessibility, or documentation structure 88 | * **Issues**: Report bugs or suggest new features via [Issues](https://github.com/mirkoschubert/datenschutz-checkliste/issues) 89 | * **Pull Requests**: Submit changes directly through [Pull Requests](https://help.github.com/articles/about-pull-requests/) 90 | 91 | **New to contributing?** Check our [Contributing Guidelines](CONTRIBUTING.md) for detailed instructions. 92 | 93 | ## Get in Touch 94 | 95 | - **Bug Reports & Feature Requests**: [GitHub Issues](https://github.com/mirkoschubert/datenschutz-checkliste/issues) 96 | - **Community Chat**: Matrix #privacy-cheat-sheet:matrix.org 97 | - **Discussions**: [GitHub Discussions](https://github.com/mirkoschubert/datenschutz-checkliste/discussions) 98 | - **Support the Project**: Buy me a coffee 99 | 100 | ## Contributors 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /app/src/lib/stores/assessment.svelte.ts: -------------------------------------------------------------------------------- 1 | import type { ChecklistItem, AssessmentResponse, Language, ComplianceLevel } from '$lib/types/gdpr'; 2 | import { gdprData, scoringConfig } from '$lib/data/gdpr-data'; 3 | 4 | class AssessmentState { 5 | responses = $state({}); 6 | ignoredItems = $state>(new Set()); 7 | currentLang = $state('en'); 8 | 9 | constructor() { 10 | if (typeof window !== 'undefined') { 11 | this.loadFromStorage(); 12 | } 13 | } 14 | 15 | get data() { 16 | return gdprData[this.currentLang]; 17 | } 18 | 19 | get allItems(): ChecklistItem[] { 20 | const items: ChecklistItem[] = []; 21 | this.data.categories.forEach((cat) => { 22 | items.push(...cat.items); 23 | }); 24 | return items; 25 | } 26 | 27 | get totalItems(): number { 28 | return this.allItems.filter((item) => !this.ignoredItems.has(item.id)).length; 29 | } 30 | 31 | get completedItems(): number { 32 | return Object.keys(this.responses).filter((id) => !this.ignoredItems.has(id)).length; 33 | } 34 | 35 | get progressPercentage(): number { 36 | return this.totalItems > 0 ? (this.completedItems / this.totalItems) * 100 : 0; 37 | } 38 | 39 | get currentScore(): number { 40 | let score = 0; 41 | this.allItems.forEach((item) => { 42 | if (this.responses[item.id] && !this.ignoredItems.has(item.id)) { 43 | const weight = item.required 44 | ? item.weight * scoringConfig.weights.required 45 | : item.weight * scoringConfig.weights.optional; 46 | score += weight; 47 | } 48 | }); 49 | return score; 50 | } 51 | 52 | get maxScore(): number { 53 | let max = 0; 54 | this.allItems.forEach((item) => { 55 | if (!this.ignoredItems.has(item.id)) { 56 | const weight = item.required 57 | ? item.weight * scoringConfig.weights.required 58 | : item.weight * scoringConfig.weights.optional; 59 | max += weight; 60 | } 61 | }); 62 | return max; 63 | } 64 | 65 | get scorePercentage(): number { 66 | return this.maxScore > 0 ? (this.currentScore / this.maxScore) * 100 : 0; 67 | } 68 | 69 | get complianceLevel(): ComplianceLevel { 70 | const score = this.scorePercentage; 71 | 72 | if (score >= scoringConfig.thresholds.excellent) return 'excellent'; 73 | if (score >= scoringConfig.thresholds.good) return 'good'; 74 | if (score >= scoringConfig.thresholds.moderate) return 'moderate'; 75 | if (score >= scoringConfig.thresholds.poor) return 'poor'; 76 | return 'critical'; 77 | } 78 | 79 | toggleItem(itemId: string) { 80 | if (this.ignoredItems.has(itemId)) return; 81 | 82 | if (this.responses[itemId]) { 83 | delete this.responses[itemId]; 84 | } else { 85 | this.responses[itemId] = true; 86 | } 87 | 88 | this.saveToStorage(); 89 | } 90 | 91 | ignoreItem(itemId: string) { 92 | this.ignoredItems.add(itemId); 93 | delete this.responses[itemId]; 94 | this.saveToStorage(); 95 | } 96 | 97 | unignoreItem(itemId: string) { 98 | this.ignoredItems.delete(itemId); 99 | this.saveToStorage(); 100 | } 101 | 102 | setLanguage(lang: Language) { 103 | this.currentLang = lang; 104 | this.saveToStorage(); 105 | } 106 | 107 | resetAssessment() { 108 | this.responses = {}; 109 | this.ignoredItems = new Set(); 110 | this.saveToStorage(); 111 | } 112 | 113 | getCategoryScores() { 114 | return this.data.categories.map((category) => { 115 | const completedCount = category.items.filter((item) => this.responses[item.id]).length; 116 | const totalCount = category.items.length; 117 | const percentage = totalCount > 0 ? (completedCount / totalCount) * 100 : 0; 118 | 119 | let level: 'low' | 'medium' | 'high' = 'low'; 120 | if (percentage >= 80) level = 'high'; 121 | else if (percentage >= 60) level = 'medium'; 122 | 123 | return { 124 | id: category.id, 125 | title: category.title, 126 | completed: completedCount, 127 | total: totalCount, 128 | percentage: Math.round(percentage), 129 | level: level 130 | }; 131 | }); 132 | } 133 | 134 | getRecommendations() { 135 | const recommendations: Array<{ 136 | title: string; 137 | category: string; 138 | description: string; 139 | priority: number; 140 | }> = []; 141 | 142 | this.data.categories.forEach((category) => { 143 | category.items.forEach((item) => { 144 | if (!this.responses[item.id] && item.required) { 145 | recommendations.push({ 146 | title: item.title, 147 | category: category.title, 148 | description: item.description.substring(0, 150) + '...', 149 | priority: item.weight 150 | }); 151 | } 152 | }); 153 | }); 154 | 155 | // Sort by priority (weight) descending 156 | recommendations.sort((a, b) => b.priority - a.priority); 157 | 158 | // Return top 5 recommendations 159 | return recommendations.slice(0, 5); 160 | } 161 | 162 | exportData() { 163 | return { 164 | version: '1.0', 165 | timestamp: new Date().toISOString(), 166 | language: this.currentLang, 167 | responses: this.responses, 168 | ignoredItems: [...this.ignoredItems], 169 | scores: { 170 | completed: this.completedItems, 171 | total: this.totalItems, 172 | currentScore: this.currentScore, 173 | maxScore: this.maxScore, 174 | percentage: this.scorePercentage 175 | } 176 | }; 177 | } 178 | 179 | importData(data: any): boolean { 180 | try { 181 | if (data.version && data.responses) { 182 | this.responses = data.responses; 183 | 184 | if (data.ignoredItems) { 185 | this.ignoredItems = new Set(data.ignoredItems); 186 | } 187 | 188 | if (data.language) { 189 | this.setLanguage(data.language); 190 | } 191 | 192 | this.saveToStorage(); 193 | return true; 194 | } 195 | } catch (error) { 196 | console.error('Error importing data:', error); 197 | } 198 | return false; 199 | } 200 | 201 | private saveToStorage() { 202 | if (typeof localStorage === 'undefined') return; 203 | 204 | localStorage.setItem('gdpr_responses', JSON.stringify(this.responses)); 205 | localStorage.setItem('gdpr_ignored', JSON.stringify([...this.ignoredItems])); 206 | localStorage.setItem('gdpr_language', this.currentLang); 207 | } 208 | 209 | private loadFromStorage() { 210 | if (typeof localStorage === 'undefined') return; 211 | 212 | // Load language 213 | const savedLang = localStorage.getItem('gdpr_language'); 214 | if (savedLang && (savedLang === 'en' || savedLang === 'de')) { 215 | this.currentLang = savedLang; 216 | } 217 | 218 | // Load responses 219 | const savedResponses = localStorage.getItem('gdpr_responses'); 220 | if (savedResponses) { 221 | try { 222 | this.responses = JSON.parse(savedResponses); 223 | } catch (e) { 224 | console.error('Error loading responses:', e); 225 | } 226 | } 227 | 228 | // Load ignored items 229 | const savedIgnored = localStorage.getItem('gdpr_ignored'); 230 | if (savedIgnored) { 231 | try { 232 | this.ignoredItems = new Set(JSON.parse(savedIgnored)); 233 | } catch (e) { 234 | console.error('Error loading ignored items:', e); 235 | } 236 | } 237 | } 238 | } 239 | 240 | export const assessment = new AssessmentState(); 241 | -------------------------------------------------------------------------------- /webapp/README.md: -------------------------------------------------------------------------------- 1 | # GDPR Compliance Checker - Interactive Web App 2 | 3 | This interactive web application provides a comprehensive GDPR compliance assessment tool for web developers and designers, inspired by digital-defense.io. 4 | 5 | ## Features 6 | 7 | ### 🎯 Interactive Assessment 8 | - **Real-time scoring**: See your compliance score update as you complete items 9 | - **Category-based organization**: Security, Hosting, External Services, Content & Features, Legal Documents 10 | - **Detailed explanations**: Each item includes legal basis, implementation tips, and best practices 11 | - **Progress tracking**: Visual progress bar and completion statistics 12 | 13 | ### 📊 Compliance Overview 14 | - **Visual scoring system**: Circular progress indicator with color-coded compliance levels 15 | - **Category breakdown**: See performance in each compliance area 16 | - **Priority recommendations**: AI-powered suggestions for improvement 17 | - **Compliance rating**: From "Critical Issues" to "Excellent Compliance" 18 | 19 | ### 💾 Export & Import 20 | - **JSON export/import**: Save and share assessments 21 | - **PDF reports**: Generate professional compliance reports 22 | - **Local storage**: Automatic progress saving 23 | - **Data portability**: Easy backup and restore 24 | 25 | ### 🌐 Multilingual Support 26 | - **English and German**: Complete translations 27 | - **Easy language switching**: Toggle between languages instantly 28 | - **Localized legal references**: Country-specific legal basis 29 | 30 | ### 📱 Modern UI/UX 31 | - **Responsive design**: Works on desktop, tablet, and mobile 32 | - **Accessibility**: WCAG compliant with keyboard navigation 33 | - **Progressive Web App**: Can be installed and used offline 34 | - **Modern design**: Clean, professional interface 35 | 36 | ## Technical Architecture 37 | 38 | ### Frontend Stack 39 | - **Pure JavaScript**: No framework dependencies 40 | - **Modern CSS**: CSS Grid, Flexbox, CSS Variables 41 | - **Web Standards**: Progressive enhancement, semantic HTML 42 | - **Responsive Design**: Mobile-first approach 43 | 44 | ### File Structure 45 | ``` 46 | webapp/ 47 | ├── index.html # Main HTML template 48 | ├── styles/ 49 | │ └── main.css # Complete CSS styling 50 | └── js/ 51 | ├── data.js # GDPR checklist data and scoring 52 | ├── assessment.js # Assessment logic and UI 53 | ├── overview.js # Results and reporting 54 | ├── export.js # Import/export functionality 55 | └── main.js # Application coordination 56 | ``` 57 | 58 | ### Key Classes 59 | 60 | #### `GDPRAssessment` 61 | - Manages checklist state and user responses 62 | - Handles category expansion/collapse 63 | - Real-time progress calculation 64 | - Local storage integration 65 | 66 | #### `OverviewManager` 67 | - Generates compliance reports 68 | - Calculates category scores 69 | - Provides recommendations 70 | - HTML report generation 71 | 72 | #### `ExportManager` 73 | - JSON export/import 74 | - PDF report generation 75 | - Data validation 76 | - Notification system 77 | 78 | #### `GDPRApp` 79 | - Application initialization 80 | - Event coordination 81 | - Navigation management 82 | - Global error handling 83 | 84 | ## Compliance Scoring System 85 | 86 | ### Scoring Algorithm 87 | ```javascript 88 | // Weighted scoring based on item importance 89 | score = Σ(item_weight × completion_status × type_multiplier) 90 | 91 | // Type multipliers 92 | required_items: 1.0 93 | optional_items: 0.5 94 | ``` 95 | 96 | ### Compliance Levels 97 | - **Excellent** (90-100%): Outstanding compliance across all areas 98 | - **Good** (75-89%): Strong compliance with minor improvements needed 99 | - **Moderate** (60-74%): Adequate compliance but several areas need attention 100 | - **Poor** (40-59%): Significant gaps requiring immediate action 101 | - **Critical** (0-39%): Major compliance issues needing urgent resolution 102 | 103 | ## Data Structure 104 | 105 | ### Assessment Item Format 106 | ```javascript 107 | { 108 | id: "unique-identifier", 109 | title: "Human-readable title", 110 | description: "Detailed explanation", 111 | required: true/false, 112 | weight: 1-10, 113 | legalBasis: ["Art. 32 GDPR", "§64 BDSG"], 114 | implementation: ["Step 1", "Step 2", ...] 115 | } 116 | ``` 117 | 118 | ### Export Format 119 | ```javascript 120 | { 121 | version: "1.0", 122 | timestamp: "2025-01-01T00:00:00.000Z", 123 | language: "en|de", 124 | responses: { item_id: true/false }, 125 | scores: { 126 | completed: number, 127 | total: number, 128 | currentScore: number, 129 | maxScore: number, 130 | percentage: number 131 | } 132 | } 133 | ``` 134 | 135 | ## Installation & Usage 136 | 137 | ### Quick Start 138 | 1. Clone the repository 139 | 2. Open `webapp/index.html` in a web browser 140 | 3. Start your assessment! 141 | 142 | ### Local Development 143 | ```bash 144 | # Serve locally (optional) 145 | cd webapp 146 | python -m http.server 8000 147 | # or 148 | npx serve . 149 | ``` 150 | 151 | ### Integration Options 152 | 153 | #### Embed in Existing Site 154 | ```html 155 | 160 | ``` 161 | 162 | #### Custom Styling 163 | The app uses CSS variables for easy customization: 164 | 165 | ```css 166 | :root { 167 | --primary-color: #your-brand-color; 168 | --accent-color: #your-accent-color; 169 | /* ... other variables */ 170 | } 171 | ``` 172 | 173 | ## Extensibility 174 | 175 | ### Adding New Compliance Areas 176 | The modular design allows easy extension for other regulations: 177 | 178 | ```javascript 179 | // Add to gdprData structure 180 | const aiActData = { 181 | categories: [ 182 | { 183 | id: "ai-risk-assessment", 184 | title: "AI Risk Assessment", 185 | items: [/* assessment items */] 186 | } 187 | ] 188 | }; 189 | ``` 190 | 191 | ### Future Enhancements 192 | - **AI Act compliance**: European AI regulation checklist 193 | - **Accessibility (WCAG)**: Web accessibility guidelines 194 | - **Copyright compliance**: Digital media usage rights 195 | - **Cookie consent**: Advanced cookie management 196 | - **Multi-site assessment**: Manage multiple website assessments 197 | 198 | ## Browser Support 199 | 200 | - **Modern browsers**: Chrome 80+, Firefox 75+, Safari 13+, Edge 80+ 201 | - **Progressive enhancement**: Basic functionality in older browsers 202 | - **Offline capability**: Service worker for offline usage 203 | - **Mobile optimized**: Touch-friendly interface 204 | 205 | ## Contributing 206 | 207 | This project is part of **Hacktoberfest 2025**! We welcome contributions: 208 | 209 | 1. **Fork the repository** 210 | 2. **Create feature branch**: `git checkout -b feature/new-feature` 211 | 3. **Make changes**: Follow the existing code style 212 | 4. **Test thoroughly**: Ensure responsiveness and functionality 213 | 5. **Submit PR**: Include detailed description 214 | 215 | ### Contribution Areas 216 | - Legal content updates 217 | - Translation improvements 218 | - UI/UX enhancements 219 | - New compliance frameworks 220 | - Accessibility improvements 221 | - Performance optimizations 222 | 223 | ## License 224 | 225 | MIT License - see LICENSE file for details. 226 | 227 | ## Disclaimer 228 | 229 | This tool provides general guidance and does not constitute legal advice. Always consult qualified legal professionals for compliance matters. 230 | 231 | --- 232 | 233 | **Created for Hacktoberfest 2025** 🎃 234 | Transforming static checklists into interactive compliance tools! -------------------------------------------------------------------------------- /app/src/lib/components/assessment/ChecklistItem.svelte: -------------------------------------------------------------------------------- 1 | 30 | 31 |
{ 35 | if (e.key === 'Enter' || e.key === ' ') { 36 | e.preventDefault(); 37 | toggleItem(); 38 | } 39 | }} 40 | role="checkbox" 41 | aria-checked={isCompleted} 42 | tabindex="0" 43 | > 44 |
45 |
46 | {#if isCompleted} 47 | 48 | 55 | 56 | {/if} 57 |
58 |
59 |
60 |

61 | {item.title} 62 | {#if item.required} 63 | Required 64 | {/if} 65 |

66 | {#if !isIgnored} 67 | 77 | {:else} 78 | 88 | {/if} 89 |
90 |

{item.description}

91 | {#if item.explanation && item.explanation.length > 0} 92 |
93 | 94 | 95 | 102 | 103 | Detailed Information 104 | 105 |
106 |
    107 | {#each item.explanation as point} 108 |
  • {point}
  • 109 | {/each} 110 |
111 | 112 | {#if item.legalBasis && item.legalBasis.length > 0} 113 | 121 | {/if} 122 | 123 | {#if item.legitimationReason && item.legitimationReason.length > 0} 124 | 132 | {/if} 133 | 134 | {#if item.implementation && item.implementation.length > 0} 135 |
136 | Implementation Tips: 137 |
    138 | {#each item.implementation as tip} 139 |
  • {tip}
  • 140 | {/each} 141 |
142 |
143 | {/if} 144 |
145 |
146 | {/if} 147 |
148 |
149 |
150 | 151 | 392 | -------------------------------------------------------------------------------- /webapp/js/overview.js: -------------------------------------------------------------------------------- 1 | // Overview functionality for displaying assessment results 2 | class OverviewManager { 3 | constructor(assessmentManager) { 4 | this.assessmentManager = assessmentManager; 5 | this.init(); 6 | } 7 | 8 | init() { 9 | this.updateOverview(); 10 | } 11 | 12 | updateOverview() { 13 | this.updateScoreCard(); 14 | this.updateCategoryBreakdown(); 15 | this.updateRecommendations(); 16 | } 17 | 18 | updateScoreCard() { 19 | const overallScoreElement = document.getElementById('overallScore'); 20 | const scoreTitleElement = document.getElementById('scoreTitle'); 21 | const scoreDescriptionElement = document.getElementById('scoreDescription'); 22 | const scoreCircleElement = document.getElementById('scoreCircle'); 23 | 24 | if (!this.assessmentManager || !overallScoreElement) return; 25 | 26 | const scorePercentage = this.assessmentManager.maxScore > 0 27 | ? (this.assessmentManager.currentScore / this.assessmentManager.maxScore) * 100 28 | : 0; 29 | 30 | const roundedScore = Math.round(scorePercentage); 31 | const complianceLevel = this.assessmentManager.getComplianceLevel(); 32 | const labels = scoringConfig.labels[this.assessmentManager.currentLang]; 33 | const descriptions = scoringConfig.descriptions[this.assessmentManager.currentLang]; 34 | 35 | // Update score number 36 | overallScoreElement.textContent = roundedScore; 37 | 38 | // Update title and description 39 | if (scoreTitleElement && scoreDescriptionElement) { 40 | scoreTitleElement.textContent = labels[complianceLevel] || labels.critical; 41 | scoreDescriptionElement.textContent = descriptions[complianceLevel] || descriptions.critical; 42 | } 43 | 44 | // Update progress circle 45 | if (scoreCircleElement) { 46 | const circumference = 2 * Math.PI * 45; // radius is 45 47 | const offset = circumference - (scorePercentage / 100) * circumference; 48 | scoreCircleElement.style.strokeDashoffset = offset; 49 | 50 | // Update color based on score 51 | let color = '#e74c3c'; // red for critical 52 | if (scorePercentage >= scoringConfig.thresholds.excellent) color = '#27ae60'; // green 53 | else if (scorePercentage >= scoringConfig.thresholds.good) color = '#42b983'; // teal 54 | else if (scorePercentage >= scoringConfig.thresholds.moderate) color = '#f39c12'; // orange 55 | else if (scorePercentage >= scoringConfig.thresholds.poor) color = '#e67e22'; // dark orange 56 | 57 | scoreCircleElement.style.stroke = color; 58 | } 59 | } 60 | 61 | updateCategoryBreakdown() { 62 | const breakdownList = document.getElementById('breakdownList'); 63 | if (!breakdownList || !this.assessmentManager) return; 64 | 65 | const categoryScores = this.assessmentManager.getCategoryScores(); 66 | 67 | breakdownList.innerHTML = categoryScores.map(category => ` 68 |
69 |
70 |
${category.title}
71 |
${category.completed}/${category.total} items completed
72 |
73 |
74 | ${category.percentage}% 75 |
76 |
77 | `).join(''); 78 | } 79 | 80 | updateRecommendations() { 81 | const recommendationsList = document.getElementById('recommendationsList'); 82 | if (!recommendationsList || !this.assessmentManager) return; 83 | 84 | const recommendations = this.assessmentManager.getRecommendations(); 85 | 86 | if (recommendations.length === 0) { 87 | recommendationsList.innerHTML = ` 88 |
89 |
Great job!
90 |
91 | You've completed all required GDPR compliance items. Consider reviewing optional items to further enhance your compliance. 92 |
93 |
94 | `; 95 | return; 96 | } 97 | 98 | recommendationsList.innerHTML = recommendations.map(recommendation => ` 99 |
100 |
101 | ${recommendation.title} 102 | (${recommendation.category}) 103 |
104 |
${recommendation.description}
105 |
Priority: ${recommendation.priority}/10
106 |
107 | `).join(''); 108 | } 109 | 110 | generateComplianceReport() { 111 | if (!this.assessmentManager) return null; 112 | 113 | const scorePercentage = this.assessmentManager.maxScore > 0 114 | ? (this.assessmentManager.currentScore / this.assessmentManager.maxScore) * 100 115 | : 0; 116 | 117 | const complianceLevel = this.assessmentManager.getComplianceLevel(); 118 | const categoryScores = this.assessmentManager.getCategoryScores(); 119 | const recommendations = this.assessmentManager.getRecommendations(); 120 | const labels = scoringConfig.labels[this.assessmentManager.currentLang]; 121 | const descriptions = scoringConfig.descriptions[this.assessmentManager.currentLang]; 122 | 123 | return { 124 | timestamp: new Date().toISOString(), 125 | language: this.assessmentManager.currentLang, 126 | overallScore: { 127 | percentage: Math.round(scorePercentage), 128 | level: complianceLevel, 129 | title: labels[complianceLevel], 130 | description: descriptions[complianceLevel] 131 | }, 132 | summary: { 133 | totalItems: this.assessmentManager.totalItems, 134 | completedItems: this.assessmentManager.completedItems, 135 | currentScore: this.assessmentManager.currentScore, 136 | maxScore: this.assessmentManager.maxScore 137 | }, 138 | categories: categoryScores, 139 | recommendations: recommendations, 140 | detailedResponses: this.assessmentManager.responses 141 | }; 142 | } 143 | 144 | generateHTMLReport() { 145 | const report = this.generateComplianceReport(); 146 | if (!report) return ''; 147 | 148 | const html = ` 149 | 150 | 151 | 152 | 153 | 154 | GDPR Compliance Report 155 | 170 | 171 | 172 |
173 |

GDPR Compliance Report

174 |

Generated on ${new Date(report.timestamp).toLocaleString()}

175 |

Disclaimer: This report provides general guidance and does not constitute legal advice. Always consult with a qualified lawyer for legal matters.

176 |
177 | 178 |
179 |
${report.overallScore.percentage}
180 |

${report.overallScore.title}

181 |

${report.overallScore.description}

182 |

${report.summary.completedItems}/${report.summary.totalItems} compliance items completed

183 |
184 | 185 |
186 |

Category Breakdown

187 |
188 | ${report.categories.map(category => ` 189 |
190 |

${category.title}

191 |
${category.percentage}%
192 |

${category.completed}/${category.total} items completed

193 |
194 | `).join('')} 195 |
196 |
197 | 198 | ${report.recommendations.length > 0 ? ` 199 |
200 |

Priority Recommendations

201 | ${report.recommendations.map(rec => ` 202 |
203 |

${rec.title}

204 |

Category: ${rec.category}

205 |

${rec.description}

206 |

Priority: ${rec.priority}/10

207 |
208 | `).join('')} 209 |
210 | ` : ` 211 |
212 |

Congratulations!

213 |

You have completed all required GDPR compliance items. Consider reviewing optional items to further enhance your compliance.

214 |
215 | `} 216 | 217 | 221 | 222 | 223 | `; 224 | 225 | return html; 226 | } 227 | } 228 | 229 | // Export for use in other modules 230 | if (typeof module !== 'undefined' && module.exports) { 231 | module.exports = OverviewManager; 232 | } -------------------------------------------------------------------------------- /app/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 | GDPR Compliance Checker 30 | 34 | 35 | 36 |
37 |
38 | 39 |
40 |
41 | 42 |
43 |

GDPR Compliance Assessment

44 |

45 | Evaluate your organization's data privacy practices and improve GDPR compliance 46 |

47 |
48 | 49 | 50 |
51 | 52 |
53 | 54 | 55 |
56 | 68 | 69 | 81 |
82 | 83 | 84 | {#if recommendations.length > 0} 85 |
86 | 87 |
(showRecommendations = !showRecommendations)} 92 | onkeydown={(e) => { 93 | if (e.key === 'Enter' || e.key === ' ') { 94 | e.preventDefault(); 95 | showRecommendations = !showRecommendations; 96 | } 97 | }} 98 | > 99 |

100 | 101 | 108 | 109 | Priority Recommendations 110 | ({recommendations.length}) 111 |

112 | 133 |
134 | 135 | {#if showRecommendations} 136 |
137 |

138 | Focus on these required items to improve your compliance score 139 |

140 | 141 |
142 | {#each recommendations as rec} 143 |
144 |
145 |

{rec.title}

146 | {rec.category} 147 |
148 |

{rec.description}

149 |
150 | {/each} 151 |
152 |
153 | {/if} 154 |
155 |
156 | {/if} 157 | 158 | 159 |
160 |

Category Overview

161 |
162 | {#each categoryScores as score} 163 | 164 |
165 |

{score.title}

166 |
167 |
171 |
172 |

173 | {score.completed}/{score.total} completed ({score.percentage}%) 174 |

175 |
176 |
177 | {/each} 178 |
179 |
180 | 181 | 182 |
183 |

Checklist Categories

184 |

185 | Click on each category to expand and review individual compliance items 186 |

187 | 188 |
189 | {#each assessment.data.categories as category (category.id)} 190 | 191 | {/each} 192 |
193 |
194 |
195 |
196 | 197 |
198 | 199 | (showExportModal = false)} /> 200 |
201 | 202 | 464 | -------------------------------------------------------------------------------- /webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | GDPR Compliance Checker - Interactive Assessment 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 | 30 | 31 | 59 |
60 |
61 |
62 | 63 | 64 |
65 |
66 | 67 |
68 |
69 |

GDPR Compliance Assessment

70 |

71 | Interactive checklist to evaluate your website's GDPR compliance. 72 | Complete all sections to get your compliance score. 73 |

74 | 75 | 76 |
77 |
78 |
79 |
80 |
81 | 0% Complete 82 | Score: 0/100 83 |
84 |
85 |
86 | 87 | 88 |
89 | 90 |
91 |
92 | 93 | 94 | 140 | 141 | 142 | 197 |
198 |
199 | 200 | 201 |
202 |
203 | 219 |
220 |
221 | 222 | 223 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | -------------------------------------------------------------------------------- /webapp/js/main.js: -------------------------------------------------------------------------------- 1 | // Main application initialization and coordination 2 | class GDPRApp { 3 | constructor() { 4 | this.assessmentManager = null; 5 | this.overviewManager = null; 6 | this.exportManager = null; 7 | 8 | this.init(); 9 | } 10 | 11 | init() { 12 | // Wait for DOM to be ready 13 | if (document.readyState === 'loading') { 14 | document.addEventListener('DOMContentLoaded', () => this.initializeApp()); 15 | } else { 16 | this.initializeApp(); 17 | } 18 | } 19 | 20 | initializeApp() { 21 | try { 22 | // Initialize managers 23 | this.assessmentManager = new GDPRAssessment(); 24 | this.overviewManager = new OverviewManager(this.assessmentManager); 25 | this.exportManager = new ExportManager(this.assessmentManager, this.overviewManager); 26 | 27 | // Make managers globally available for event handlers 28 | window.gdprAssessment = this.assessmentManager; 29 | window.overviewManager = this.overviewManager; 30 | window.exportManager = this.exportManager; 31 | 32 | // Backwards compatibility 33 | window.assessmentManager = this.assessmentManager; 34 | 35 | // Setup global event listeners 36 | this.setupEventListeners(); 37 | 38 | // Check for shared data in URL 39 | if (this.exportManager.checkForSharedData) { 40 | this.exportManager.checkForSharedData(); 41 | } 42 | 43 | console.log('GDPR Compliance Checker initialized successfully'); 44 | } catch (error) { 45 | console.error('Error initializing GDPR app:', error); 46 | this.showError('Failed to initialize application. Please refresh the page.'); 47 | } 48 | } 49 | 50 | setupEventListeners() { 51 | // Navigation 52 | this.setupNavigation(); 53 | 54 | // Language switching 55 | this.setupLanguageSwitching(); 56 | 57 | // Theme switching 58 | this.setupThemeSwitching(); 59 | 60 | // Mobile navigation 61 | this.setupMobileNavigation(); 62 | 63 | // Keyboard navigation 64 | this.setupKeyboardNavigation(); 65 | 66 | // Window events 67 | this.setupWindowEvents(); 68 | } 69 | 70 | setupNavigation() { 71 | const navLinks = document.querySelectorAll('.nav-link'); 72 | 73 | navLinks.forEach(link => { 74 | link.addEventListener('click', (e) => { 75 | e.preventDefault(); 76 | const targetSection = link.getAttribute('href').substring(1); 77 | this.showSection(targetSection); 78 | 79 | // Update active nav link 80 | navLinks.forEach(l => l.classList.remove('active')); 81 | link.classList.add('active'); 82 | }); 83 | }); 84 | } 85 | 86 | setupLanguageSwitching() { 87 | // Language switching is now handled by the assessment manager 88 | // This method is kept for backwards compatibility 89 | } 90 | 91 | setupThemeSwitching() { 92 | const themeToggle = document.getElementById('themeToggle'); 93 | if (!themeToggle) return; 94 | 95 | // Initialize theme from localStorage or system preference 96 | this.initializeTheme(); 97 | 98 | // Setup theme toggle button 99 | themeToggle.addEventListener('click', () => { 100 | const currentTheme = document.documentElement.getAttribute('data-theme'); 101 | const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; 102 | this.setTheme(newTheme); 103 | }); 104 | } 105 | 106 | initializeTheme() { 107 | // Check if user previously set a theme preference 108 | const savedTheme = localStorage.getItem('gdpr_theme'); 109 | 110 | if (savedTheme) { 111 | // Use saved preference 112 | this.setTheme(savedTheme); 113 | } else { 114 | // Check system preference 115 | if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { 116 | this.setTheme('dark'); 117 | } else { 118 | this.setTheme('light'); 119 | } 120 | 121 | // Listen for system preference changes 122 | window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { 123 | const newTheme = e.matches ? 'dark' : 'light'; 124 | this.setTheme(newTheme); 125 | }); 126 | } 127 | } 128 | 129 | setTheme(theme) { 130 | document.documentElement.setAttribute('data-theme', theme); 131 | localStorage.setItem('gdpr_theme', theme); 132 | 133 | // Update icon visibility is handled by CSS 134 | console.log(`Theme set to: ${theme}`); 135 | } 136 | 137 | setupMobileNavigation() { 138 | const navToggle = document.querySelector('.nav-toggle'); 139 | const navList = document.querySelector('.nav-list'); 140 | 141 | if (navToggle && navList) { 142 | navToggle.addEventListener('click', () => { 143 | navList.classList.toggle('active'); 144 | 145 | // Update icon 146 | const icon = navToggle.querySelector('i'); 147 | if (navList.classList.contains('active')) { 148 | icon.className = 'fas fa-times'; 149 | } else { 150 | icon.className = 'fas fa-bars'; 151 | } 152 | }); 153 | 154 | // Close mobile nav when clicking on a link 155 | navList.addEventListener('click', (e) => { 156 | if (e.target.classList.contains('nav-link')) { 157 | navList.classList.remove('active'); 158 | navToggle.querySelector('i').className = 'fas fa-bars'; 159 | } 160 | }); 161 | } 162 | } 163 | 164 | setupKeyboardNavigation() { 165 | // Add keyboard navigation support 166 | document.addEventListener('keydown', (e) => { 167 | // Ctrl/Cmd + S to save/export 168 | if ((e.ctrlKey || e.metaKey) && e.key === 's') { 169 | e.preventDefault(); 170 | this.exportManager.exportJSON(); 171 | } 172 | 173 | // Ctrl/Cmd + O to import 174 | if ((e.ctrlKey || e.metaKey) && e.key === 'o') { 175 | e.preventDefault(); 176 | document.getElementById('importFile').click(); 177 | } 178 | }); 179 | } 180 | 181 | setupWindowEvents() { 182 | // Auto-save on page unload 183 | window.addEventListener('beforeunload', () => { 184 | if (this.assessmentManager) { 185 | this.assessmentManager.saveResponses(); 186 | } 187 | }); 188 | 189 | // Handle window resize for responsive adjustments 190 | window.addEventListener('resize', this.debounce(() => { 191 | this.handleResize(); 192 | }, 250)); 193 | 194 | // Handle online/offline status 195 | window.addEventListener('online', () => { 196 | this.showNotification('Connection restored', 'success'); 197 | }); 198 | 199 | window.addEventListener('offline', () => { 200 | this.showNotification('Working offline - your progress is saved locally', 'info'); 201 | }); 202 | } 203 | 204 | showSection(sectionId) { 205 | // Hide all sections 206 | document.querySelectorAll('.section').forEach(section => { 207 | section.classList.add('hidden'); 208 | }); 209 | 210 | // Show target section 211 | const targetSection = document.getElementById(sectionId); 212 | if (targetSection) { 213 | targetSection.classList.remove('hidden'); 214 | 215 | // Update overview when showing overview section 216 | if (sectionId === 'overview' && this.overviewManager) { 217 | this.overviewManager.updateOverview(); 218 | } 219 | 220 | // Scroll to top 221 | window.scrollTo({ top: 0, behavior: 'smooth' }); 222 | } 223 | } 224 | 225 | handleResize() { 226 | // Handle responsive adjustments if needed 227 | const width = window.innerWidth; 228 | 229 | if (width < 768) { 230 | // Mobile adjustments 231 | this.adjustForMobile(); 232 | } else { 233 | // Desktop adjustments 234 | this.adjustForDesktop(); 235 | } 236 | } 237 | 238 | adjustForMobile() { 239 | // Close mobile nav if open 240 | const navList = document.querySelector('.nav-list'); 241 | const navToggle = document.querySelector('.nav-toggle'); 242 | 243 | if (navList && navList.classList.contains('active')) { 244 | navList.classList.remove('active'); 245 | if (navToggle) { 246 | navToggle.querySelector('i').className = 'fas fa-bars'; 247 | } 248 | } 249 | } 250 | 251 | adjustForDesktop() { 252 | // Ensure desktop nav is visible 253 | const navList = document.querySelector('.nav-list'); 254 | if (navList) { 255 | navList.classList.remove('active'); 256 | } 257 | } 258 | 259 | showError(message) { 260 | const errorDiv = document.createElement('div'); 261 | errorDiv.className = 'error-message'; 262 | errorDiv.innerHTML = ` 263 |
264 | 265 | ${message} 266 | 269 |
270 | `; 271 | 272 | document.body.appendChild(errorDiv); 273 | 274 | // Auto-remove after 10 seconds 275 | setTimeout(() => { 276 | if (errorDiv.parentNode) { 277 | errorDiv.remove(); 278 | } 279 | }, 10000); 280 | } 281 | 282 | showNotification(message, type = 'info') { 283 | if (this.exportManager) { 284 | this.exportManager.showNotification(message, type); 285 | } 286 | } 287 | 288 | // Utility function for debouncing 289 | debounce(func, wait) { 290 | let timeout; 291 | return function executedFunction(...args) { 292 | const later = () => { 293 | clearTimeout(timeout); 294 | func(...args); 295 | }; 296 | clearTimeout(timeout); 297 | timeout = setTimeout(later, wait); 298 | }; 299 | } 300 | } 301 | 302 | // Global utility functions 303 | function showAbout() { 304 | const aboutContent = ` 305 | 337 | `; 338 | 339 | const modal = document.createElement('div'); 340 | modal.id = 'aboutModal'; 341 | modal.className = 'modal active'; 342 | modal.innerHTML = aboutContent; 343 | 344 | document.body.appendChild(modal); 345 | 346 | // Close on backdrop click 347 | modal.addEventListener('click', (e) => { 348 | if (e.target === modal) { 349 | closeAbout(); 350 | } 351 | }); 352 | } 353 | 354 | function closeAbout() { 355 | const modal = document.getElementById('aboutModal'); 356 | if (modal) { 357 | modal.remove(); 358 | } 359 | } 360 | 361 | function showLegal() { 362 | const legalContent = ` 363 | 387 | `; 388 | 389 | const modal = document.createElement('div'); 390 | modal.id = 'legalModal'; 391 | modal.className = 'modal active'; 392 | modal.innerHTML = legalContent; 393 | 394 | document.body.appendChild(modal); 395 | 396 | // Close on backdrop click 397 | modal.addEventListener('click', (e) => { 398 | if (e.target === modal) { 399 | closeLegal(); 400 | } 401 | }); 402 | } 403 | 404 | function closeLegal() { 405 | const modal = document.getElementById('legalModal'); 406 | if (modal) { 407 | modal.remove(); 408 | } 409 | } 410 | 411 | // Initialize the application 412 | const app = new GDPRApp(); 413 | 414 | // Make app globally available for debugging 415 | window.gdprApp = app; 416 | 417 | // Global functions for backwards compatibility and event handlers 418 | function toggleCategory(categoryId) { 419 | if (window.gdprAssessment && window.gdprAssessment.toggleCategory) { 420 | window.gdprAssessment.toggleCategory(categoryId); 421 | } 422 | } 423 | 424 | function handleItemCheckboxClick(itemId, event) { 425 | if (event && event.target.tagName === 'INPUT') { 426 | // If clicking directly on checkbox, let the onchange handle it 427 | return; 428 | } 429 | if (window.gdprAssessment && window.gdprAssessment.handleCheckboxClick) { 430 | window.gdprAssessment.handleCheckboxClick(itemId); 431 | } 432 | } 433 | 434 | function handleItemCheckboxChange(itemId) { 435 | if (window.gdprAssessment && window.gdprAssessment.handleCheckboxChange) { 436 | window.gdprAssessment.handleCheckboxChange(itemId); 437 | } 438 | } 439 | 440 | function handleItemCheckbox(itemId) { 441 | // Backwards compatibility 442 | handleItemCheckboxClick(itemId); 443 | } 444 | 445 | function handleCheckboxChange(itemId, isChecked) { 446 | // Backwards compatibility 447 | handleItemCheckboxChange(itemId); 448 | } 449 | 450 | function showItemDetails(itemId) { 451 | if (window.gdprAssessment && window.gdprAssessment.showItemDetails) { 452 | window.gdprAssessment.showItemDetails(itemId); 453 | } 454 | } 455 | 456 | function handleItemIgnore(itemId) { 457 | if (window.gdprAssessment && window.gdprAssessment.toggleIgnoreItem) { 458 | window.gdprAssessment.toggleIgnoreItem(itemId); 459 | } 460 | } -------------------------------------------------------------------------------- /webapp/js/assessment.js: -------------------------------------------------------------------------------- 1 | // Assessment functionality for the GDPR compliance checker 2 | class GDPRAssessment { 3 | constructor() { 4 | this.currentLang = 'en'; 5 | this.responses = {}; 6 | this.ignoredItems = new Set(); 7 | this.totalItems = 0; 8 | this.completedItems = 0; 9 | this.currentScore = 0; 10 | this.maxScore = 0; 11 | 12 | this.init(); 13 | } 14 | 15 | init() { 16 | this.loadSavedData(); 17 | this.renderCategories(); 18 | this.updateProgress(); 19 | this.setupLanguageDropdown(); 20 | } 21 | 22 | loadSavedData() { 23 | // Get language from localStorage or default to English 24 | this.currentLang = localStorage.getItem('gdpr_language') || 'en'; 25 | 26 | // Load saved responses and ignored items 27 | const savedResponses = localStorage.getItem('gdpr_responses'); 28 | if (savedResponses) { 29 | this.responses = JSON.parse(savedResponses); 30 | } 31 | 32 | const savedIgnored = localStorage.getItem('gdpr_ignored'); 33 | if (savedIgnored) { 34 | this.ignoredItems = new Set(JSON.parse(savedIgnored)); 35 | } 36 | 37 | this.updateLanguageDisplay(); 38 | } 39 | 40 | saveData() { 41 | localStorage.setItem('gdpr_language', this.currentLang); 42 | localStorage.setItem('gdpr_responses', JSON.stringify(this.responses)); 43 | localStorage.setItem('gdpr_ignored', JSON.stringify([...this.ignoredItems])); 44 | } 45 | 46 | setupLanguageDropdown() { 47 | const dropdown = document.getElementById('languageToggle'); 48 | const menu = document.getElementById('languageMenu'); 49 | 50 | if (!dropdown || !menu) return; 51 | 52 | dropdown.addEventListener('click', (e) => { 53 | e.stopPropagation(); 54 | dropdown.classList.toggle('active'); 55 | menu.classList.toggle('active'); 56 | }); 57 | 58 | // Close dropdown when clicking outside 59 | document.addEventListener('click', () => { 60 | dropdown.classList.remove('active'); 61 | menu.classList.remove('active'); 62 | }); 63 | 64 | // Language selection 65 | menu.addEventListener('click', (e) => { 66 | if (e.target.classList.contains('lang-btn')) { 67 | const lang = e.target.dataset.lang; 68 | this.setLanguage(lang); 69 | dropdown.classList.remove('active'); 70 | menu.classList.remove('active'); 71 | } 72 | }); 73 | } 74 | 75 | updateLanguageDisplay() { 76 | const currentLangSpan = document.querySelector('.current-lang'); 77 | if (currentLangSpan) { 78 | const langMap = { 79 | 'en': '🇬🇧 English', 80 | 'de': '🇩🇪 Deutsch' 81 | }; 82 | currentLangSpan.textContent = langMap[this.currentLang] || '🇬🇧 English'; 83 | } 84 | 85 | // Update active state in dropdown 86 | document.querySelectorAll('.lang-btn').forEach(btn => { 87 | btn.classList.toggle('active', btn.dataset.lang === this.currentLang); 88 | }); 89 | } 90 | 91 | setLanguage(lang) { 92 | this.currentLang = lang; 93 | this.updateLanguageDisplay(); 94 | this.renderCategories(); 95 | this.updateProgress(); 96 | this.saveData(); 97 | } 98 | 99 | renderCategories() { 100 | const container = document.getElementById('categoriesContainer'); 101 | if (!container) return; 102 | 103 | const data = gdprData[this.currentLang]; 104 | if (!data) return; 105 | 106 | container.innerHTML = ''; 107 | 108 | data.categories.forEach(category => { 109 | const categoryElement = this.createCategoryElement(category); 110 | container.appendChild(categoryElement); 111 | }); 112 | 113 | this.calculateTotalItems(); 114 | } 115 | 116 | createCategoryElement(category) { 117 | const categoryDiv = document.createElement('div'); 118 | categoryDiv.className = 'category'; 119 | categoryDiv.dataset.categoryId = category.id; 120 | 121 | // Calculate category progress 122 | const categoryItems = category.items; 123 | const completedCount = categoryItems.filter(item => this.responses[item.id]).length; 124 | const totalCount = categoryItems.length; 125 | const progressPercentage = totalCount > 0 ? (completedCount / totalCount) * 100 : 0; 126 | 127 | categoryDiv.innerHTML = ` 128 |
129 |
130 | 131 | ${category.title} 132 |
133 |
134 | ${completedCount}/${totalCount} completed 135 | 138 |
139 |
140 |
141 | ${this.createCategoryItems(category.items)} 142 |
143 | `; 144 | 145 | return categoryDiv; 146 | } 147 | 148 | createCategoryItems(items) { 149 | return items.map(item => ` 150 |
152 |
153 | 160 |
161 |
162 |
${item.title}
163 |
${item.description}
164 |
165 | 168 | 173 | ${item.required ? 'Required' : 'Optional'} 174 |
175 |
176 |
177 | `).join(''); 178 | } 179 | 180 | toggleCategory(categoryId) { 181 | const content = document.getElementById(`category-${categoryId}`); 182 | const toggle = content.parentElement.querySelector('.category-toggle i'); 183 | 184 | if (content.classList.contains('expanded')) { 185 | content.classList.remove('expanded'); 186 | toggle.style.transform = 'rotate(0deg)'; 187 | } else { 188 | content.classList.add('expanded'); 189 | toggle.style.transform = 'rotate(180deg)'; 190 | } 191 | } 192 | 193 | handleCheckboxClick(itemId) { 194 | // Don't allow checking if item is ignored 195 | if (this.ignoredItems.has(itemId)) return; 196 | 197 | const checkbox = document.getElementById(`checkbox-${itemId}`); 198 | if (!checkbox) return; 199 | 200 | // Toggle the checkbox state 201 | checkbox.checked = !checkbox.checked; 202 | 203 | // Update the response state 204 | this.updateItemResponse(itemId, checkbox.checked); 205 | } 206 | 207 | handleCheckboxChange(itemId) { 208 | // Don't allow checking if item is ignored 209 | if (this.ignoredItems.has(itemId)) return; 210 | 211 | const checkbox = document.getElementById(`checkbox-${itemId}`); 212 | if (!checkbox) return; 213 | 214 | // Use the current checkbox state 215 | this.updateItemResponse(itemId, checkbox.checked); 216 | } 217 | 218 | updateItemResponse(itemId, isChecked) { 219 | if (isChecked) { 220 | this.responses[itemId] = true; 221 | } else { 222 | delete this.responses[itemId]; 223 | } 224 | 225 | // Update item visual state 226 | const itemElement = document.querySelector(`[data-item-id="${itemId}"]`); 227 | if (itemElement) { 228 | itemElement.classList.toggle('completed', isChecked); 229 | } 230 | 231 | // Update progress and save 232 | this.updateProgress(); 233 | this.saveData(); 234 | } 235 | 236 | toggleIgnoreItem(itemId) { 237 | if (this.ignoredItems.has(itemId)) { 238 | // Unignore item 239 | this.ignoredItems.delete(itemId); 240 | } else { 241 | // Ignore item - also uncheck if checked 242 | this.ignoredItems.add(itemId); 243 | if (this.responses[itemId]) { 244 | delete this.responses[itemId]; 245 | } 246 | } 247 | 248 | // Re-render to update UI 249 | this.renderCategories(); 250 | this.updateProgress(); 251 | this.saveData(); 252 | } 253 | 254 | showItemDetails(itemId) { 255 | // Find the item data 256 | const data = gdprData[this.currentLang]; 257 | let itemData = null; 258 | 259 | for (const category of data.categories) { 260 | const item = category.items.find(i => i.id === itemId); 261 | if (item) { 262 | itemData = item; 263 | break; 264 | } 265 | } 266 | 267 | if (!itemData) return; 268 | 269 | // Create modal content 270 | const modal = this.createItemModal(itemData); 271 | document.body.appendChild(modal); 272 | 273 | // Show modal 274 | setTimeout(() => modal.classList.add('active'), 10); 275 | } 276 | 277 | createItemModal(item) { 278 | const modal = document.createElement('div'); 279 | modal.className = 'modal-overlay'; 280 | modal.onclick = (e) => { 281 | if (e.target === modal) this.closeModal(modal); 282 | }; 283 | 284 | const legalBasisHtml = item.legalBasis ? ` 285 | 291 | ` : ''; 292 | 293 | const implementationHtml = item.implementation ? ` 294 | 300 | ` : ''; 301 | 302 | modal.innerHTML = ` 303 | 322 | `; 323 | 324 | return modal; 325 | } 326 | 327 | closeModal(modal) { 328 | modal.classList.remove('active'); 329 | setTimeout(() => modal.remove(), 300); 330 | } 331 | 332 | showItemDetails(itemId) { 333 | const item = this.findItemById(itemId); 334 | if (!item) return; 335 | 336 | const modal = document.getElementById('itemModal'); 337 | const modalTitle = document.getElementById('modalTitle'); 338 | const modalDescription = document.getElementById('modalDescription'); 339 | const modalLegal = document.getElementById('modalLegal'); 340 | const modalImplementation = document.getElementById('modalImplementation'); 341 | 342 | modalTitle.textContent = item.title; 343 | modalDescription.textContent = item.description; 344 | 345 | // Legal basis section 346 | modalLegal.innerHTML = ` 347 |

Legal Basis

348 |
    349 | ${item.legalBasis.map(basis => `
  • ${basis}
  • `).join('')} 350 |
351 | `; 352 | 353 | // Implementation tips 354 | modalImplementation.innerHTML = ` 355 |

Implementation Tips

356 |
    357 | ${item.implementation.map(tip => `
  • ${tip}
  • `).join('')} 358 |
359 | `; 360 | 361 | modal.classList.add('active'); 362 | 363 | // Focus management for accessibility 364 | modal.querySelector('.modal-close').focus(); 365 | } 366 | 367 | closeModal() { 368 | const modal = document.getElementById('itemModal'); 369 | modal.classList.remove('active'); 370 | } 371 | 372 | findItemById(itemId) { 373 | const data = gdprData[this.currentLang]; 374 | if (!data) return null; 375 | 376 | for (const category of data.categories) { 377 | const item = category.items.find(item => item.id === itemId); 378 | if (item) return item; 379 | } 380 | return null; 381 | } 382 | 383 | calculateTotalItems() { 384 | const data = gdprData[this.currentLang]; 385 | if (!data) return; 386 | 387 | this.totalItems = 0; 388 | this.maxScore = 0; 389 | 390 | data.categories.forEach(category => { 391 | category.items.forEach(item => { 392 | // Only count non-ignored items 393 | if (!this.ignoredItems.has(item.id)) { 394 | this.totalItems++; 395 | const weight = item.required ? item.weight * scoringConfig.weights.required : item.weight * scoringConfig.weights.optional; 396 | this.maxScore += weight; 397 | } 398 | }); 399 | }); 400 | } 401 | 402 | updateProgress() { 403 | // Recalculate totals to account for ignored items 404 | this.calculateTotalItems(); 405 | 406 | this.completedItems = Object.keys(this.responses).filter(id => !this.ignoredItems.has(id)).length; 407 | const progressPercentage = this.totalItems > 0 ? (this.completedItems / this.totalItems) * 100 : 0; 408 | 409 | // Calculate weighted score 410 | this.currentScore = 0; 411 | const data = gdprData[this.currentLang]; 412 | 413 | if (data) { 414 | data.categories.forEach(category => { 415 | category.items.forEach(item => { 416 | if (this.responses[item.id]) { 417 | const weight = item.required ? item.weight * scoringConfig.weights.required : item.weight * scoringConfig.weights.optional; 418 | this.currentScore += weight; 419 | } 420 | }); 421 | }); 422 | } 423 | 424 | const scorePercentage = this.maxScore > 0 ? (this.currentScore / this.maxScore) * 100 : 0; 425 | 426 | // Update progress bar 427 | const progressFill = document.getElementById('progressFill'); 428 | const progressText = document.getElementById('progressText'); 429 | const progressScore = document.getElementById('progressScore'); 430 | 431 | if (progressFill) { 432 | progressFill.style.width = `${progressPercentage}%`; 433 | } 434 | 435 | if (progressText) { 436 | progressText.textContent = `${Math.round(progressPercentage)}% Complete`; 437 | } 438 | 439 | if (progressScore) { 440 | progressScore.textContent = `Score: ${Math.round(scorePercentage)}/100`; 441 | } 442 | 443 | // Update category progress 444 | this.updateCategoryProgress(); 445 | } 446 | 447 | updateCategoryProgress() { 448 | const data = gdprData[this.currentLang]; 449 | if (!data) return; 450 | 451 | data.categories.forEach(category => { 452 | const categoryElement = document.querySelector(`[data-category-id="${category.id}"]`); 453 | if (categoryElement) { 454 | const completedCount = category.items.filter(item => this.responses[item.id]).length; 455 | const totalCount = category.items.length; 456 | 457 | const progressElement = categoryElement.querySelector('.category-progress'); 458 | if (progressElement) { 459 | progressElement.textContent = `${completedCount}/${totalCount} completed`; 460 | } 461 | } 462 | }); 463 | } 464 | 465 | saveResponses() { 466 | localStorage.setItem('gdpr_responses', JSON.stringify(this.responses)); 467 | localStorage.setItem('gdpr_last_updated', new Date().toISOString()); 468 | } 469 | 470 | loadSavedResponses() { 471 | const saved = localStorage.getItem('gdpr_responses'); 472 | if (saved) { 473 | try { 474 | this.responses = JSON.parse(saved); 475 | this.updateProgress(); 476 | 477 | // Update checkboxes 478 | Object.keys(this.responses).forEach(itemId => { 479 | const checkbox = document.getElementById(`checkbox-${itemId}`); 480 | if (checkbox) { 481 | checkbox.checked = true; 482 | } 483 | 484 | const itemElement = document.querySelector(`[data-item-id="${itemId}"]`); 485 | if (itemElement) { 486 | itemElement.classList.add('completed'); 487 | } 488 | }); 489 | } catch (error) { 490 | console.error('Error loading saved responses:', error); 491 | } 492 | } 493 | } 494 | 495 | resetAssessment() { 496 | if (confirm('Are you sure you want to reset all responses? This action cannot be undone.')) { 497 | this.responses = {}; 498 | localStorage.removeItem('gdpr_responses'); 499 | localStorage.removeItem('gdpr_last_updated'); 500 | 501 | // Update UI 502 | document.querySelectorAll('.item-checkbox').forEach(checkbox => { 503 | checkbox.checked = false; 504 | }); 505 | 506 | document.querySelectorAll('.checklist-item').forEach(item => { 507 | item.classList.remove('completed'); 508 | }); 509 | 510 | this.updateProgress(); 511 | 512 | if (window.overviewManager) { 513 | window.overviewManager.updateOverview(); 514 | } 515 | } 516 | } 517 | 518 | exportData() { 519 | const exportData = { 520 | version: "1.0", 521 | timestamp: new Date().toISOString(), 522 | language: this.currentLang, 523 | responses: this.responses, 524 | scores: { 525 | completed: this.completedItems, 526 | total: this.totalItems, 527 | currentScore: this.currentScore, 528 | maxScore: this.maxScore, 529 | percentage: this.maxScore > 0 ? (this.currentScore / this.maxScore) * 100 : 0 530 | } 531 | }; 532 | 533 | return exportData; 534 | } 535 | 536 | importData(importData) { 537 | try { 538 | if (importData.version && importData.responses) { 539 | this.responses = importData.responses; 540 | 541 | if (importData.language) { 542 | this.setLanguage(importData.language); 543 | } 544 | 545 | this.saveResponses(); 546 | this.renderCategories(); 547 | this.updateProgress(); 548 | 549 | if (window.overviewManager) { 550 | window.overviewManager.updateOverview(); 551 | } 552 | 553 | return true; 554 | } 555 | } catch (error) { 556 | console.error('Error importing data:', error); 557 | } 558 | return false; 559 | } 560 | 561 | getComplianceLevel() { 562 | const scorePercentage = this.maxScore > 0 ? (this.currentScore / this.maxScore) * 100 : 0; 563 | 564 | if (scorePercentage >= scoringConfig.thresholds.excellent) return 'excellent'; 565 | if (scorePercentage >= scoringConfig.thresholds.good) return 'good'; 566 | if (scorePercentage >= scoringConfig.thresholds.moderate) return 'moderate'; 567 | if (scorePercentage >= scoringConfig.thresholds.poor) return 'poor'; 568 | return 'critical'; 569 | } 570 | 571 | getCategoryScores() { 572 | const data = gdprData[this.currentLang]; 573 | if (!data) return []; 574 | 575 | return data.categories.map(category => { 576 | const completedCount = category.items.filter(item => this.responses[item.id]).length; 577 | const totalCount = category.items.length; 578 | const percentage = totalCount > 0 ? (completedCount / totalCount) * 100 : 0; 579 | 580 | let level = 'low'; 581 | if (percentage >= 80) level = 'high'; 582 | else if (percentage >= 60) level = 'medium'; 583 | 584 | return { 585 | id: category.id, 586 | title: category.title, 587 | completed: completedCount, 588 | total: totalCount, 589 | percentage: Math.round(percentage), 590 | level: level 591 | }; 592 | }); 593 | } 594 | 595 | getRecommendations() { 596 | const data = gdprData[this.currentLang]; 597 | if (!data) return []; 598 | 599 | const recommendations = []; 600 | 601 | data.categories.forEach(category => { 602 | category.items.forEach(item => { 603 | if (!this.responses[item.id] && item.required) { 604 | recommendations.push({ 605 | title: item.title, 606 | category: category.title, 607 | description: `${item.description.substring(0, 100)}...`, 608 | priority: item.weight 609 | }); 610 | } 611 | }); 612 | }); 613 | 614 | // Sort by priority (weight) descending 615 | recommendations.sort((a, b) => b.priority - a.priority); 616 | 617 | // Return top 5 recommendations 618 | return recommendations.slice(0, 5); 619 | } 620 | } 621 | 622 | // Global functions for event handling 623 | function toggleCategory(categoryId) { 624 | if (window.assessmentManager) { 625 | window.assessmentManager.toggleCategory(categoryId); 626 | } 627 | } 628 | 629 | function handleCheckboxChange(itemId, isChecked) { 630 | if (window.assessmentManager) { 631 | window.assessmentManager.handleCheckboxChange(itemId, isChecked); 632 | } 633 | } 634 | 635 | function showItemDetails(itemId) { 636 | if (window.assessmentManager) { 637 | window.assessmentManager.showItemDetails(itemId); 638 | } 639 | } 640 | 641 | function closeModal() { 642 | if (window.assessmentManager) { 643 | window.assessmentManager.closeModal(); 644 | } 645 | } 646 | 647 | // Close modal on ESC key 648 | document.addEventListener('keydown', function(event) { 649 | if (event.key === 'Escape') { 650 | closeModal(); 651 | } 652 | }); 653 | 654 | // Close modal on backdrop click 655 | document.addEventListener('click', function(event) { 656 | const modal = document.getElementById('itemModal'); 657 | if (event.target === modal) { 658 | closeModal(); 659 | } 660 | }); 661 | 662 | // Export for use in other modules 663 | if (typeof module !== 'undefined' && module.exports) { 664 | module.exports = GDPRAssessment; 665 | } -------------------------------------------------------------------------------- /webapp/js/export.js: -------------------------------------------------------------------------------- 1 | // Export and import functionality 2 | class ExportManager { 3 | constructor(assessmentManager, overviewManager) { 4 | this.assessmentManager = assessmentManager; 5 | this.overviewManager = overviewManager; 6 | this.init(); 7 | } 8 | 9 | init() { 10 | this.setupEventListeners(); 11 | } 12 | 13 | setupEventListeners() { 14 | // Export JSON button 15 | const exportJsonBtn = document.getElementById('exportJson'); 16 | if (exportJsonBtn) { 17 | exportJsonBtn.addEventListener('click', () => this.exportJSON()); 18 | } 19 | 20 | // Export PDF button 21 | const exportPdfBtn = document.getElementById('exportPdf'); 22 | if (exportPdfBtn) { 23 | exportPdfBtn.addEventListener('click', () => this.exportDirectPDF()); 24 | } 25 | 26 | // Check PDF library availability 27 | this.checkPDFLibrary(); 28 | 29 | // Import button 30 | const importBtn = document.getElementById('importBtn'); 31 | const importFile = document.getElementById('importFile'); 32 | 33 | if (importBtn && importFile) { 34 | importBtn.addEventListener('click', () => importFile.click()); 35 | importFile.addEventListener('change', (e) => this.importJSON(e)); 36 | } 37 | 38 | // Reset button 39 | const resetBtn = document.getElementById('resetBtn'); 40 | if (resetBtn) { 41 | resetBtn.addEventListener('click', () => this.resetAssessment()); 42 | } 43 | } 44 | 45 | checkPDFLibrary() { 46 | // Check if jsPDF is loaded, if not try to load it dynamically 47 | if (!window.jsPDF && !window.jspdf) { 48 | console.log('jsPDF not found, checking if script is loaded...'); 49 | 50 | // Wait a bit for scripts to load, then check again 51 | setTimeout(() => { 52 | if (!window.jsPDF && !window.jspdf) { 53 | console.warn('jsPDF library not loaded. PDF export may not work.'); 54 | 55 | // Try to load jsPDF dynamically as fallback 56 | const script = document.createElement('script'); 57 | script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js'; 58 | script.onload = () => { 59 | console.log('jsPDF loaded dynamically'); 60 | }; 61 | script.onerror = () => { 62 | console.error('Failed to load jsPDF dynamically'); 63 | }; 64 | document.head.appendChild(script); 65 | } 66 | }, 1000); 67 | } else { 68 | console.log('jsPDF library is available'); 69 | } 70 | } 71 | 72 | exportJSON() { 73 | if (!this.assessmentManager) return; 74 | 75 | try { 76 | const exportData = this.assessmentManager.exportData(); 77 | const jsonString = JSON.stringify(exportData, null, 2); 78 | const blob = new Blob([jsonString], { type: 'application/json' }); 79 | 80 | const url = URL.createObjectURL(blob); 81 | const link = document.createElement('a'); 82 | link.href = url; 83 | link.download = `gdpr-assessment-${this.formatDate(new Date())}.json`; 84 | 85 | document.body.appendChild(link); 86 | link.click(); 87 | document.body.removeChild(link); 88 | 89 | URL.revokeObjectURL(url); 90 | 91 | this.showNotification('Assessment exported successfully!', 'success'); 92 | } catch (error) { 93 | console.error('Export error:', error); 94 | this.showNotification('Error exporting assessment', 'error'); 95 | } 96 | } 97 | 98 | exportDirectPDF() { 99 | console.log('Starting direct PDF export...'); 100 | 101 | // Check if jsPDF is available 102 | if (!window.jsPDF && !window.jspdf) { 103 | this.showNotification('PDF library not loaded. Please refresh the page.', 'error'); 104 | console.error('jsPDF library not found. Make sure the script is loaded.'); 105 | return; 106 | } 107 | 108 | try { 109 | // Try different possible jsPDF references 110 | let jsPDF; 111 | 112 | if (window.jspdf && window.jspdf.jsPDF) { 113 | jsPDF = window.jspdf.jsPDF; 114 | console.log('Using window.jspdf.jsPDF'); 115 | } else if (window.jsPDF) { 116 | jsPDF = window.jsPDF; 117 | console.log('Using window.jsPDF'); 118 | } else { 119 | throw new Error('jsPDF library not found'); 120 | } 121 | 122 | const doc = new jsPDF(); 123 | 124 | // Get assessment data from localStorage and checklist structure 125 | const assessmentData = this.getAssessmentData(); 126 | const currentLang = localStorage.getItem('gdpr_language') || 'en'; 127 | 128 | console.log('Assessment data loaded:', assessmentData.length, 'items'); 129 | 130 | // Calculate statistics 131 | const totalItems = assessmentData.length; 132 | const completedItems = assessmentData.filter(item => item.completed).length; 133 | const ignoredItems = assessmentData.filter(item => item.ignored).length; 134 | const pendingItems = assessmentData.filter(item => !item.completed && !item.ignored).length; 135 | const score = totalItems > 0 ? Math.round((completedItems / totalItems) * 100) : 0; 136 | 137 | // Set up document header 138 | doc.setFontSize(20); 139 | doc.text('GDPR Compliance Assessment Report', 20, 30); 140 | 141 | doc.setFontSize(12); 142 | doc.text(`Generated on: ${new Date().toLocaleDateString()}`, 20, 45); 143 | doc.text(`Language: ${currentLang.toUpperCase()}`, 20, 55); 144 | 145 | // Overall Score 146 | doc.setFontSize(16); 147 | doc.text('Overall Compliance Score', 20, 75); 148 | doc.setFontSize(24); 149 | 150 | // Color based on score 151 | if (score >= 80) { 152 | doc.setTextColor(40, 167, 69); // Green 153 | } else if (score >= 60) { 154 | doc.setTextColor(255, 193, 7); // Yellow 155 | } else { 156 | doc.setTextColor(220, 53, 69); // Red 157 | } 158 | doc.text(`${score}%`, 20, 95); 159 | doc.setTextColor(0, 0, 0); // Reset to black 160 | 161 | // Summary statistics 162 | let yPos = 115; 163 | doc.setFontSize(14); 164 | doc.text('Summary Statistics:', 20, yPos); 165 | yPos += 15; 166 | 167 | doc.setFontSize(11); 168 | doc.text(`Total Items: ${totalItems}`, 25, yPos); 169 | yPos += 8; 170 | doc.text(`Completed: ${completedItems}`, 25, yPos); 171 | yPos += 8; 172 | doc.text(`Ignored: ${ignoredItems}`, 25, yPos); 173 | yPos += 8; 174 | doc.text(`Pending: ${pendingItems}`, 25, yPos); 175 | yPos += 20; 176 | 177 | // Group items by category for breakdown 178 | const categoryMap = new Map(); 179 | assessmentData.forEach(item => { 180 | if (!categoryMap.has(item.category)) { 181 | categoryMap.set(item.category, { 182 | total: 0, 183 | completed: 0, 184 | ignored: 0 185 | }); 186 | } 187 | const cat = categoryMap.get(item.category); 188 | cat.total++; 189 | if (item.completed) cat.completed++; 190 | if (item.ignored) cat.ignored++; 191 | }); 192 | 193 | // Category Breakdown 194 | doc.setFontSize(14); 195 | doc.text('Category Breakdown:', 20, yPos); 196 | yPos += 15; 197 | 198 | doc.setFontSize(11); 199 | for (const [categoryName, stats] of categoryMap) { 200 | if (yPos > 250) { 201 | doc.addPage(); 202 | yPos = 20; 203 | } 204 | const categoryScore = stats.total > 0 ? Math.round((stats.completed / stats.total) * 100) : 0; 205 | doc.text(`${categoryName}: ${categoryScore}% (${stats.completed}/${stats.total} completed)`, 25, yPos); 206 | yPos += 10; 207 | } 208 | 209 | // Pending Items (recommendations) 210 | const pendingItemsList = assessmentData.filter(item => !item.completed && !item.ignored); 211 | if (pendingItemsList.length > 0) { 212 | yPos += 10; 213 | if (yPos > 240) { 214 | doc.addPage(); 215 | yPos = 20; 216 | } 217 | 218 | doc.setFontSize(14); 219 | doc.text('Priority Recommendations:', 20, yPos); 220 | yPos += 15; 221 | 222 | doc.setFontSize(11); 223 | // Show top 10 pending items, prioritize required ones 224 | const prioritizedPending = pendingItemsList 225 | .sort((a, b) => (b.required ? 1 : 0) - (a.required ? 1 : 0)) 226 | .slice(0, 10); 227 | 228 | prioritizedPending.forEach((item, index) => { 229 | if (yPos > 250) { 230 | doc.addPage(); 231 | yPos = 20; 232 | } 233 | 234 | const prefix = item.required ? '[Required]' : '[Optional]'; 235 | const title = `${prefix} ${item.title}`; 236 | const lines = doc.splitTextToSize(`${index + 1}. ${title}`, 170); 237 | doc.text(lines, 25, yPos); 238 | yPos += lines.length * 6 + 3; 239 | }); 240 | } 241 | 242 | // Save the PDF 243 | const timestamp = new Date().toISOString().split('T')[0]; 244 | doc.save(`gdpr-assessment-${timestamp}.pdf`); 245 | 246 | this.showNotification('PDF report generated successfully!', 'success'); 247 | console.log('PDF export completed successfully'); 248 | } catch (error) { 249 | console.error('Direct PDF export error:', error); 250 | 251 | // Fallback to browser print dialog 252 | if (confirm('PDF generation failed. Would you like to use your browser\'s print function instead?\n\nClick OK to open print dialog, or Cancel to try again later.')) { 253 | this.fallbackToPrint(); 254 | } else { 255 | this.showNotification('PDF export failed. Try refreshing the page.', 'error'); 256 | } 257 | } 258 | } 259 | 260 | exportPDF() { 261 | // Fallback method for browser printing 262 | if (!this.overviewManager) return; 263 | 264 | try { 265 | // Generate HTML report 266 | const htmlReport = this.overviewManager.generateHTMLReport(); 267 | 268 | // Create a new window to display the report for printing 269 | const printWindow = window.open('', '_blank'); 270 | printWindow.document.write(htmlReport); 271 | printWindow.document.close(); 272 | 273 | // Wait for content to load, then trigger print dialog 274 | printWindow.onload = () => { 275 | setTimeout(() => { 276 | printWindow.print(); 277 | }, 100); 278 | }; 279 | 280 | this.showNotification('Report opened for printing/PDF export', 'success'); 281 | } catch (error) { 282 | console.error('PDF export error:', error); 283 | this.showNotification('Error generating PDF report', 'error'); 284 | } 285 | } 286 | 287 | importJSON(event) { 288 | const file = event.target.files[0]; 289 | if (!file) return; 290 | 291 | if (file.type !== 'application/json') { 292 | this.showNotification('Please select a valid JSON file', 'error'); 293 | return; 294 | } 295 | 296 | const reader = new FileReader(); 297 | reader.onload = (e) => { 298 | try { 299 | const importData = JSON.parse(e.target.result); 300 | 301 | if (this.validateImportData(importData)) { 302 | const success = this.assessmentManager.importData(importData); 303 | 304 | if (success) { 305 | this.showNotification('Assessment imported successfully!', 'success'); 306 | } else { 307 | this.showNotification('Error importing assessment data', 'error'); 308 | } 309 | } else { 310 | this.showNotification('Invalid assessment file format', 'error'); 311 | } 312 | } catch (error) { 313 | console.error('Import error:', error); 314 | this.showNotification('Error reading assessment file', 'error'); 315 | } 316 | }; 317 | 318 | reader.readAsText(file); 319 | 320 | // Reset file input 321 | event.target.value = ''; 322 | } 323 | 324 | validateImportData(data) { 325 | // Check for required properties 326 | if (!data || typeof data !== 'object') return false; 327 | if (!data.version || !data.responses) return false; 328 | if (typeof data.responses !== 'object') return false; 329 | 330 | return true; 331 | } 332 | 333 | resetAssessment() { 334 | if (this.assessmentManager) { 335 | this.assessmentManager.resetAssessment(); 336 | } 337 | } 338 | 339 | formatDate(date) { 340 | const year = date.getFullYear(); 341 | const month = String(date.getMonth() + 1).padStart(2, '0'); 342 | const day = String(date.getDate()).padStart(2, '0'); 343 | const hours = String(date.getHours()).padStart(2, '0'); 344 | const minutes = String(date.getMinutes()).padStart(2, '0'); 345 | 346 | return `${year}-${month}-${day}_${hours}-${minutes}`; 347 | } 348 | 349 | showNotification(message, type = 'info') { 350 | // Create notification element 351 | const notification = document.createElement('div'); 352 | notification.className = `notification notification-${type}`; 353 | notification.innerHTML = ` 354 |
355 | 356 | ${message} 357 |
358 | 361 | `; 362 | 363 | // Add styles if not already present 364 | this.addNotificationStyles(); 365 | 366 | // Add to page 367 | document.body.appendChild(notification); 368 | 369 | // Auto-remove after 5 seconds 370 | setTimeout(() => { 371 | if (notification.parentNode) { 372 | notification.remove(); 373 | } 374 | }, 5000); 375 | 376 | // Add entrance animation 377 | setTimeout(() => { 378 | notification.classList.add('notification-show'); 379 | }, 10); 380 | } 381 | 382 | getNotificationIcon(type) { 383 | switch (type) { 384 | case 'success': return 'fa-check-circle'; 385 | case 'error': return 'fa-exclamation-circle'; 386 | case 'warning': return 'fa-exclamation-triangle'; 387 | default: return 'fa-info-circle'; 388 | } 389 | } 390 | 391 | addNotificationStyles() { 392 | if (document.getElementById('notification-styles')) return; 393 | 394 | const style = document.createElement('style'); 395 | style.id = 'notification-styles'; 396 | style.textContent = ` 397 | .notification { 398 | position: fixed; 399 | top: 20px; 400 | right: 20px; 401 | background: white; 402 | border-radius: 8px; 403 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); 404 | z-index: 3000; 405 | display: flex; 406 | align-items: center; 407 | justify-content: space-between; 408 | padding: 16px; 409 | min-width: 300px; 410 | max-width: 400px; 411 | opacity: 0; 412 | transform: translateX(100%); 413 | transition: all 0.3s ease; 414 | } 415 | 416 | .notification-show { 417 | opacity: 1; 418 | transform: translateX(0); 419 | } 420 | 421 | .notification-content { 422 | display: flex; 423 | align-items: center; 424 | gap: 12px; 425 | flex: 1; 426 | } 427 | 428 | .notification-success { 429 | border-left: 4px solid #27ae60; 430 | } 431 | 432 | .notification-success .fa-check-circle { 433 | color: #27ae60; 434 | } 435 | 436 | .notification-error { 437 | border-left: 4px solid #e74c3c; 438 | } 439 | 440 | .notification-error .fa-exclamation-circle { 441 | color: #e74c3c; 442 | } 443 | 444 | .notification-warning { 445 | border-left: 4px solid #f39c12; 446 | } 447 | 448 | .notification-warning .fa-exclamation-triangle { 449 | color: #f39c12; 450 | } 451 | 452 | .notification-info { 453 | border-left: 4px solid #3498db; 454 | } 455 | 456 | .notification-info .fa-info-circle { 457 | color: #3498db; 458 | } 459 | 460 | .notification-close { 461 | background: none; 462 | border: none; 463 | color: #666; 464 | cursor: pointer; 465 | padding: 4px; 466 | border-radius: 4px; 467 | transition: all 0.2s ease; 468 | } 469 | 470 | .notification-close:hover { 471 | background: #f0f0f0; 472 | color: #333; 473 | } 474 | 475 | @media (max-width: 480px) { 476 | .notification { 477 | right: 10px; 478 | left: 10px; 479 | min-width: auto; 480 | max-width: none; 481 | } 482 | } 483 | `; 484 | 485 | document.head.appendChild(style); 486 | } 487 | 488 | // Utility method to generate shareable report link 489 | generateShareableLink() { 490 | if (!this.assessmentManager) return null; 491 | 492 | try { 493 | const exportData = this.assessmentManager.exportData(); 494 | const compressed = this.compressData(exportData); 495 | 496 | // In a real implementation, you might upload to a server and get a short URL 497 | // For now, we'll use URL parameters (limited by URL length) 498 | const baseUrl = window.location.origin + window.location.pathname; 499 | const shareUrl = `${baseUrl}?data=${encodeURIComponent(compressed)}`; 500 | 501 | if (shareUrl.length > 2000) { 502 | this.showNotification('Assessment too large to share via URL', 'warning'); 503 | return null; 504 | } 505 | 506 | return shareUrl; 507 | } catch (error) { 508 | console.error('Error generating shareable link:', error); 509 | return null; 510 | } 511 | } 512 | 513 | compressData(data) { 514 | // Simple compression - in a real app you might use a proper compression library 515 | return btoa(JSON.stringify(data)); 516 | } 517 | 518 | decompressData(compressed) { 519 | try { 520 | return JSON.parse(atob(compressed)); 521 | } catch (error) { 522 | console.error('Error decompressing data:', error); 523 | return null; 524 | } 525 | } 526 | 527 | // Check for shared data in URL parameters 528 | checkForSharedData() { 529 | const urlParams = new URLSearchParams(window.location.search); 530 | const sharedData = urlParams.get('data'); 531 | 532 | if (sharedData) { 533 | const decompressed = this.decompressData(sharedData); 534 | if (decompressed && this.validateImportData(decompressed)) { 535 | // Ask user if they want to load the shared assessment 536 | const loadShared = confirm('This link contains a GDPR assessment. Would you like to load it?'); 537 | if (loadShared) { 538 | const success = this.assessmentManager.importData(decompressed); 539 | if (success) { 540 | this.showNotification('Shared assessment loaded successfully!', 'success'); 541 | // Clean up URL 542 | window.history.replaceState({}, document.title, window.location.pathname); 543 | } 544 | } 545 | } 546 | } 547 | } 548 | 549 | getAssessmentData() { 550 | // Get current language 551 | const currentLang = localStorage.getItem('gdpr_language') || 'en'; 552 | 553 | // Get saved responses and ignored items from localStorage 554 | const savedResponses = JSON.parse(localStorage.getItem('gdpr_responses') || '{}'); 555 | const savedIgnored = JSON.parse(localStorage.getItem('gdpr_ignored') || '[]'); 556 | const ignoredSet = new Set(savedIgnored); 557 | 558 | // Get the data for current language from gdprData 559 | const data = window.gdprData[currentLang]; 560 | if (!data) { 561 | console.error('No data found for language:', currentLang); 562 | return []; 563 | } 564 | 565 | // Create flat array of all items with their status 566 | const assessmentData = []; 567 | 568 | data.categories.forEach(category => { 569 | category.items.forEach(item => { 570 | assessmentData.push({ 571 | id: item.id, 572 | title: item.title, 573 | description: item.description, 574 | category: category.title, 575 | required: item.required, 576 | completed: !!savedResponses[item.id], 577 | ignored: ignoredSet.has(item.id), 578 | weight: item.weight || 1 579 | }); 580 | }); 581 | }); 582 | 583 | return assessmentData; 584 | } 585 | 586 | fallbackToPrint() { 587 | console.log('Using browser print fallback...'); 588 | 589 | // Create a printable version of the assessment 590 | const printWindow = window.open('', '_blank'); 591 | const assessmentData = this.getAssessmentData(); 592 | 593 | const printContent = ` 594 | 595 | 596 | 597 | GDPR Assessment Report 598 | 617 | 618 | 619 |
620 |

GDPR Compliance Assessment Report

621 |

Generated on: ${new Date().toLocaleDateString()}

622 |
623 | 624 |
625 |

Summary

626 |

Total Items: ${assessmentData.length}

627 |

Completed: ${assessmentData.filter(item => item.completed).length}

628 |

Ignored: ${assessmentData.filter(item => item.ignored).length}

629 |

Pending: ${assessmentData.filter(item => !item.completed && !item.ignored).length}

630 |
631 | 632 |
633 |

Assessment Details

634 | ${assessmentData.map((item, index) => { 635 | let statusClass = 'pending'; 636 | let statusText = 'Pending'; 637 | 638 | if (item.completed) { 639 | statusClass = 'completed'; 640 | statusText = 'Completed'; 641 | } else if (item.ignored) { 642 | statusClass = 'ignored'; 643 | statusText = 'Ignored'; 644 | } 645 | 646 | return ` 647 |
648 |
649 |
650 | ${index + 1}. ${item.text} 651 |
652 | ${statusText} 653 |
654 |
655 | `; 656 | }).join('')} 657 |
658 | 659 |
660 | 661 | 662 |
663 | 664 | `; 665 | 666 | printWindow.document.write(printContent); 667 | printWindow.document.close(); 668 | 669 | // Auto-trigger print dialog after a short delay 670 | setTimeout(() => { 671 | printWindow.print(); 672 | }, 500); 673 | } 674 | } 675 | 676 | // Export for use in other modules 677 | if (typeof module !== 'undefined' && module.exports) { 678 | module.exports = ExportManager; 679 | } --------------------------------------------------------------------------------