diff --git a/demo-04/README.md b/demo-04/README.md new file mode 100644 index 0000000..3065350 --- /dev/null +++ b/demo-04/README.md @@ -0,0 +1,49 @@ +# Demo 04: Signature List Generator - Code Cleanup + +## Purpose + +This is a static web application that turns student lists exported from UNIGRAZonline / CAMPUSonline (as "CSV for Excel") into nicely formatted signature lists for checking attendance. Everything happens locally in the browser - no sensitive data is transmitted anywhere. + +**Features:** +- Upload CSV student lists exported from UNIGRAZonline +- Filter by group name (manually entered or extracted from file) +- Customizable course name and date +- Include/exclude Uni Graz logo +- Print-ready signature lists +- Privacy-first: all processing happens client-side + +## Technology Stack + +- **Frontend:** Pure HTML5, CSS3, and vanilla JavaScript +- **Styling:** Bootstrap 5.0.2 (via CDN) +- **CSV Parsing:** PapaParse 5.4.1 (via CDN) +- **Fonts:** Nunito Sans (Uni Graz corporate font) + +## Task: Code Cleanup + +This codebase needs cleanup and modernization. Your goal is to iteratively improve the codebase by: + +1. **Fixing bugs and issues** (syntax errors, missing semicolons, etc.) +2. **Modernizing JavaScript** (replace `var` with `const`/`let`, use modern patterns) +3. **Adding error handling** (invalid CSV, missing columns, edge cases) +4. **Improving accessibility** (ARIA labels, keyboard navigation) +5. **Code quality improvements** (consistent styling, remove duplication, better structure) + +## Process + +Work with the agent to identify issues and iteratively improve the code. For each change: +- Explain what you're fixing and why +- Get the agent's approval before making changes +- Test the changes to ensure nothing breaks +- Document your changes in a CHANGES.md file + +## Files + +- `index.html` - Main HTML structure +- `main.css` - Styling and print styles +- `main.js` - Application logic +- `logo.png` - Uni Graz logo + +## Deployment + +This is a static site - simply upload all files to a web server. No build process or backend required. diff --git a/demo-04/index.html b/demo-04/index.html new file mode 100644 index 0000000..7f2ff95 --- /dev/null +++ b/demo-04/index.html @@ -0,0 +1,120 @@ + + + + + + + + + + Signature List Generator + + + +
+

Signature List Generator

+

+ Important: + Everything happens in your browser and locally on your computer, + no sensitive data is transmitted anywhere. +

+
+
+
+ + +
+
+
+
+ + + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ +
+
+
+
+
+
+ +
+
+ + + + + + + + + + + +
NameUnterschrift
+ + +
+
+ + + + + + diff --git a/demo-04/logo.png b/demo-04/logo.png new file mode 100644 index 0000000..1c26c2b Binary files /dev/null and b/demo-04/logo.png differ diff --git a/demo-04/main.css b/demo-04/main.css new file mode 100644 index 0000000..499c282 --- /dev/null +++ b/demo-04/main.css @@ -0,0 +1,116 @@ +:root { + --unigraz-primary: #ffd400; +} + +@font-face { + font-family: nunito_sans; + font-display: swap; + font-style: normal; + font-display: block; + font-weight: 300; + src: url(https://static.uni-graz.at/dist/unigraz/fonts/Nunito_Sans/nunito-sans-v12-latin_latin-ext-300.eot); + src: url(https://static.uni-graz.at/dist/unigraz/fonts/Nunito_Sans/nunito-sans-v12-latin_latin-ext-300.eot?#iefix) format("embedded-opentype"),url(https://static.uni-graz.at/dist/unigraz/fonts/Nunito_Sans/nunito-sans-v12-latin_latin-ext-300.woff2) format("woff2"),url(https://static.uni-graz.at/dist/unigraz/fonts/Nunito_Sans/nunito-sans-v12-latin_latin-ext-300.woff) format("woff"),url(https://static.uni-graz.at/dist/unigraz/fonts/Nunito_Sans/nunito-sans-v12-latin_latin-ext-300.ttf) format("truetype"),url(https://static.uni-graz.at/dist/unigraz/fonts/Nunito_Sans/nunito-sans-v12-latin_latin-ext-300.svg#NunitoSans) format("svg") +} + +@font-face { + font-display: swap; + font-family: nunito_sans; + font-style: italic; + font-display: block; + font-weight: 300; + src: url(https://static.uni-graz.at/dist/unigraz/fonts/Nunito_Sans/nunito-sans-v12-latin_latin-ext-300italic.eot); + src: url(https://static.uni-graz.at/dist/unigraz/fonts/Nunito_Sans/nunito-sans-v12-latin_latin-ext-300italic.eot?#iefix) format("embedded-opentype"),url(https://static.uni-graz.at/dist/unigraz/fonts/Nunito_Sans/nunito-sans-v12-latin_latin-ext-300italic.woff2) format("woff2"),url(https://static.uni-graz.at/dist/unigraz/fonts/Nunito_Sans/nunito-sans-v12-latin_latin-ext-300italic.woff) format("woff"),url(https://static.uni-graz.at/dist/unigraz/fonts/Nunito_Sans/nunito-sans-v12-latin_latin-ext-300italic.ttf) format("truetype"),url(https://static.uni-graz.at/dist/unigraz/fonts/Nunito_Sans/nunito-sans-v12-latin_latin-ext-300italic.svg#NunitoSans) format("svg") +} + +body { + font-family: nunito_sans; +} + +.bg-unigraz { + background-color: var(--unigraz-primary); +} + +#printbtn { + flex: 1; +} + +.btn-unigraz { + color: #000; + background-color: var(--unigraz-primary); + border-color: var(--unigraz-primary); +} + +.btn-unigraz:active, .btn-unigraz:hover { + color: #000; + background-color: #ffe24f; + border-color: #ffe24f; +} + +.navbar-brand { + font-weight: 600; +} + +.navbar-light .navbar-nav .nav-link { + color: rgba(0, 0, 0, .85); +} + +#logo { + float: right; +} + +table { + width: 100%; +} + +th { + border-right: 1px solid black; + border-bottom: 2px solid black; + padding: 5px; +} + +td { + height: 0.8cm; + font-size: small; + border-right: 1px solid black; + border-bottom: 1px solid black; + padding: 5px; +} + +tr > th:last-child, td:last-child { + border-right: none; +} + +tr > td:first-child { + width: 20%; + white-space: nowrap; + padding-right: 15px; +} + +tfoot td { + font-size: smaller; + border-bottom: none; +} + +#list-footer { + display: flex; +} + +#foot-group-date { + float: right; +} + +@media print { + table { + margin-bottom: 10cm; + } + .navbar, .generator-input, #contactModal { + display: none !important; + } + .container { + max-width: 90%; + } + #list-footer { + position: fixed; + bottom: -0.25cm; + } +} diff --git a/demo-04/main.js b/demo-04/main.js new file mode 100644 index 0000000..78b6608 --- /dev/null +++ b/demo-04/main.js @@ -0,0 +1,120 @@ +var DATA_STORE = {}; + +function populateTable() { + let file_list = document.getElementById('fileinput').files; + if (file_list.length > 0) { + let csv_file = file_list[0]; + Papa.parse(csv_file, { + header: true, + complete: function(results) { + DATA_STORE['participants'] = results.data.filter(teilnehmer => teilnehmer["Familienname"] !== undefined); + + + DATA_STORE['groupnames'] = new Set(DATA_STORE['participants'].map(teilnehmer => teilnehmer["Gruppe"])); + const group_select = document.getElementById('groupname-select'); + group_select.innerHTML = ''; + var default_option = document.createElement('option'); + default_option.text = "Choose Group..."; + default_option.selected = true; + group_select.appendChild(default_option); + DATA_STORE['groupnames'].forEach(group => { + var option = document.createElement('option'); + option.value = group; + option.text = group; + group_select.appendChild(option); + }); + + updateCaptions(); + updateTable(); + + + } + }); + } +} + +function updateTable(filter_group) { + const table_body = document.getElementById('table-body'); + table_body.innerHTML = ''; + DATA_STORE["participants"].forEach(teilnehmer => { + if (filter_group === undefined || (filter_group !== undefined && teilnehmer["Gruppe"] == filter_group)) { + var table_row = document.createElement('tr'); + var name = document.createElement('td'); + name.innerHTML = teilnehmer["Familienname"] + ", " + teilnehmer["Vorname"]; + table_row.appendChild(name); + table_row.appendChild(document.createElement('td')); + table_body.appendChild(table_row); + } + }, + false + ); +} + +function updateCaptions() { + let lvname = document.getElementById('lvname').value; + document.getElementById('list-caption').innerHTML = lvname; + document.getElementById('foot-title').innerHTML = lvname; + + var groupname, subcaption; + if (document.getElementById('group_from_file').checked) { + groupname = document.getElementById('groupname-select').value; + } else { + + groupname = document.getElementById('groupname').value; + } + + let listdate = document.getElementById('date').value; + if (listdate != "") { + listdate = new Date(listdate); + subcaption = `${groupname}, ${listdate.toLocaleDateString('de-AT')}`; + } else { + subcaption = groupname; + } + document.getElementById('list-subcaption').innerHTML = subcaption; + document.getElementById('foot-group-date').innerHTML = subcaption; +} + +document.getElementById('fileinput').addEventListener('change', populateTable); + +document.querySelectorAll('input, select').forEach( + elem => elem.addEventListener('input', updateCaptions), + false +); + +document.querySelectorAll('select').forEach( + elem => elem.addEventListener('input', () => { + updateCaptions(); + updateTable(elem.value); + }), + false +); + + +document.getElementById('group_from_file').addEventListener('change', (evt) => { + const groupname_input = document.getElementById('groupname'); + const groupname_select = document.getElementById('groupname-select'); + if (evt.target.checked) { + groupname_input.disabled = true; + groupname_input.hidden = true; + groupname_select.disabled = false; + groupname_select.hidden = false; + } else { + groupname_input.disabled = false; + groupname_input.hidden = false; + groupname_select.hidden = true; + groupname_select.disabled = true; + updateTable(); + } +}); + +document.getElementById('include_logo').addEventListener('change', (evt) => { + if (evt.target.checked) { + document.getElementById('logo').style['display'] = 'block'; + } else { + document.getElementById('logo').style['display'] = 'none'; + } +}); + +document.addEventListener("DOMContentLoaded", function() { + updateCaptions(); +});