001package com.studentgui.apppages; 002 003import java.awt.BorderLayout; 004import java.awt.Font; 005import java.awt.GridBagConstraints; 006import java.awt.GridBagLayout; 007import java.awt.Insets; 008import java.awt.event.ActionEvent; 009import java.awt.event.KeyEvent; 010import java.sql.SQLException; 011import java.time.LocalDate; 012import java.util.List; 013 014import javax.swing.JButton; 015import javax.swing.JLabel; 016import javax.swing.JOptionPane; 017import javax.swing.JPanel; 018import javax.swing.JScrollPane; 019import javax.swing.SwingUtilities; 020 021import org.slf4j.Logger; 022import org.slf4j.LoggerFactory; 023 024/** 025 * Digital literacy and computer skills assessment page. 026 * 027 * <p>Evaluates foundational technology competencies required for academic and professional 028 * success in digital environments. Covers 27 skills organized into 5 progressive competency domains:</p> 029 * 030 * <ul> 031 * <li><b>Phase 1 (P1_1–P1_9): Device Basics and Navigation</b> 032 * <ul> 033 * <li>Powering devices on/off, accessibility feature activation (VoiceOver/TalkBack/Narrator)</li> 034 * <li>Touch/mouse gestures for app launching and navigation</li> 035 * <li>Home screen organization, icon identification, and app launching</li> 036 * <li>Document creation, saving, and retrieval workflows</li> 037 * <li>Online resource access (web portals, learning management systems)</li> 038 * <li>Basic keyboarding (home row, touch typing fundamentals)</li> 039 * <li>UI element interaction (buttons, menus, text fields, sliders)</li> 040 * <li>System-level navigation (Control Center, App Switcher, Task Manager, Dock)</li> 041 * </ul> 042 * </li> 043 * <li><b>Phase 2 (P2_1–P2_6): Word Processing Fundamentals</b> 044 * <ul> 045 * <li>Creating, editing, and saving text documents</li> 046 * <li>Reading and navigating documents using assistive technology or visual scanning</li> 047 * <li>Menu bar and toolbar interaction for formatting and commands</li> 048 * <li>Text selection, highlighting, copy/paste workflows</li> 049 * <li>Image insertion and manipulation (copy, paste, resize, position)</li> 050 * <li>Proofreading strategies and editing for clarity/correctness</li> 051 * </ul> 052 * </li> 053 * <li><b>Phase 3 (P3_1–P3_3): Spreadsheet Fundamentals</b> 054 * <ul> 055 * <li>Describing spreadsheet structure (rows, columns, cells, sheets)</li> 056 * <li>Spreadsheet terminology (cell references, formulas, functions, ranges)</li> 057 * <li>Data entry and editing (typing, autofill, formula entry)</li> 058 * </ul> 059 * </li> 060 * <li><b>Phase 4 (P4_1–P4_5): Presentation Software</b> 061 * <ul> 062 * <li>Presentation tool concepts (slides, layouts, templates)</li> 063 * <li>Creating structured presentations (title, content, transitions)</li> 064 * <li>Editing slides (text, formatting, reordering)</li> 065 * <li>Presenting slides effectively (presenter view, navigation, notes)</li> 066 * <li>Sharing presentations (export, cloud upload, email)</li> 067 * </ul> 068 * </li> 069 * <li><b>Phase 5 (P5_1–P5_5): Digital Citizenship and Ethics</b> 070 * <ul> 071 * <li>Acceptable Use Policies (school/workplace technology guidelines)</li> 072 * <li>Digital citizenship principles (respectful communication, netiquette)</li> 073 * <li>Internet safety (phishing, malware, safe browsing)</li> 074 * <li>Copyright awareness (fair use, attribution, Creative Commons)</li> 075 * <li>Plagiarism recognition and avoidance (paraphrasing, citations, originality)</li> 076 * </ul> 077 * </li> 078 * </ul> 079 * 080 * <p><b>Data Persistence and Report Generation:</b></p> 081 * <ul> 082 * <li>Scores captured via {@link com.studentgui.uicomp.PhaseScoreField} (integer 0–4 typical)</li> 083 * <li>Persisted to normalized schema via {@link com.studentgui.apphelpers.Database#insertAssessmentResults}</li> 084 * <li>JSON export: {@code StudentDataFiles/<student>/Sessions/DigitalLiteracy/DigitalLiteracy-<sessionId>-<timestamp>.json}</li> 085 * <li>Phase-grouped time-series plots: {@code plots/DigitalLiteracy-<sessionId>-<date>-P<N>.png} (5 phase groups)</li> 086 * <li>Markdown and HTML reports with embedded plots and color-coded legends</li> 087 * </ul> 088 * 089 * <p>The shared {@link JLineGraph} visualizes recent session trends grouped by phase prefix. 090 * Implements {@link com.studentgui.app.DateChangeListener} and {@link com.studentgui.app.StudentChangeListener} 091 * for dynamic updates when global selections change.</p> 092 * 093 * <p><b>Note:</b> Skill codes and phases intentionally overlap with {@link IOS} to allow 094 * cross-platform skill mapping. Some assessment items are device-agnostic and track the same 095 * underlying competencies across iOS, Windows, macOS, and ChromeOS environments.</p> 096 * 097 * @see com.studentgui.apphelpers.Database 098 * @see JLineGraph 099 * @see com.studentgui.uicomp.PhaseScoreField 100 * @see IOS 101 */ 102public class DigitalLiteracy extends JPanel implements com.studentgui.app.DateChangeListener, com.studentgui.app.StudentChangeListener { 103 private static final Logger LOG = LoggerFactory.getLogger(DigitalLiteracy.class); 104 /** Array of input fields for each digital literacy skill part. */ 105 private final com.studentgui.uicomp.PhaseScoreField[] skillFields; 106 /** Canonical list of digital literacy assessment parts: code and display label. */ 107 private final String[][] parts; 108 109 /** Shared graph used to visualize recent digital literacy sessions. */ 110 private final JLineGraph lineGraph; // Reference to the JLineGraph instance 111 112 /** Selected student's display name (may be null) for saving/fetching data. */ 113 private String studentNameParam; 114 /** Title label shown at the top of the Digital Literacy page. */ 115 private JLabel titleLabel; 116 /** Base title text for the page; used when building the header string. */ 117 private final String baseTitle = "Digital Literacy Skills Progression"; 118 119 /** Session date to associate with persisted digital literacy progress. */ 120 private LocalDate dateParam; 121 122 /** 123 * Construct the Digital Literacy page for the given student and date. 124 * 125 * @param studentName display name of the selected student (may be null) 126 * @param date session date to associate with persisted progress 127 * @param lineGraph shared graph component used to display recent results 128 */ 129 public DigitalLiteracy(final String studentName, final LocalDate date, final JLineGraph lineGraph) { 130 this.studentNameParam = (studentName == null || studentName.trim().isEmpty()) ? com.studentgui.apphelpers.Helpers.defaultStudent() : studentName; 131 this.dateParam = date; 132 this.lineGraph = lineGraph; // Use the passed in graph instance 133 setLayout(new BorderLayout()); 134 135 this.parts = new String[][]{ 136 {"P1_1","1.1 Turn Device On/Off"},{"P1_2","1.2 Turn VoiceOver On/Off"},{"P1_3","1.3 Gestures to Click Icons"},{"P1_4","1.4 Home Screen Icons to Open Documents"},{"P1_5","1.5 Save Documents"},{"P1_6","1.6 Online Tools/Resources"},{"P1_7","1.7 Keyboarding"},{"P1_8","1.8 Use Different Elements"},{"P1_9","1.9 Control Center, App Switcher..."}, 137 {"P2_1","2.1 Write, edit save"},{"P2_2","2.2 Read, Navigate Document"},{"P2_3","2.3 Use Menubar"},{"P2_4","2.4 Highlight text, copy and paste text"},{"P2_5","2.5 Copy and paste images"},{"P2_6","2.6 Proofread and edit"}, 138 {"P3_1","3.1 Describe Spreadsheet"},{"P3_2","3.2 Explain terms and concepts"},{"P3_3","3.3 Enter/Edit data"}, 139 {"P4_1","4.1 Presentation Tools"},{"P4_2","4.2 Create Slides"},{"P4_3","4.3 Edit Slides"},{"P4_4","4.4 Present Slides"},{"P4_5","4.5 Share Slides"}, 140 {"P5_1","5.1 Acceptable Use"},{"P5_2","5.2 Digital Citizenship"},{"P5_3","5.3 Internet Safety"},{"P5_4","5.4 Copyright"},{"P5_5","5.5 Plagiarism"} 141 }; 142 143 // Panel for data entry 144 JPanel dataEntryPanel = new JPanel(); 145 dataEntryPanel.setLayout(new GridBagLayout()); 146 JScrollPane dataEntryScrollPane = new JScrollPane(dataEntryPanel); 147 dataEntryScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); 148 dataEntryScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 149 150 GridBagConstraints gbc = new GridBagConstraints(); 151 gbc.insets = new Insets(2, 2, 2, 2); 152 gbc.fill = GridBagConstraints.HORIZONTAL; 153 gbc.weightx = 1.0; 154 gbc.weighty = 0.0; 155 156 this.titleLabel = new JLabel(baseTitle); 157 this.titleLabel.setFont(this.titleLabel.getFont().deriveFont(Font.BOLD, 28f)); 158 gbc.gridx = 0; 159 gbc.gridy = 0; 160 gbc.gridwidth = GridBagConstraints.REMAINDER; 161 dataEntryPanel.add(this.titleLabel, gbc); 162 163 gbc.gridy = 1; 164 gbc.gridwidth = GridBagConstraints.REMAINDER; 165 gbc.ipady = 20; 166 dataEntryPanel.add(new JPanel(), gbc); 167 168 // layout spacing handled by PhaseScoreField 169 170 String[] labels = java.util.Arrays.stream(parts).map(x->x[1]).toArray(String[]::new); 171 int maxPx = com.studentgui.uicomp.PhaseScoreField.computeMaxLabelPixelWidth(com.studentgui.uicomp.PhaseScoreField.getLabelFont(), labels); 172 com.studentgui.uicomp.PhaseScoreField.setGlobalLabelWidth(Math.min(320, Math.max(140, maxPx + 50))); 173 skillFields = new com.studentgui.uicomp.PhaseScoreField[this.parts.length]; 174 for (int i = 0; i < this.parts.length; i++) { 175 gbc.gridy = i + 2; 176 gbc.gridx = 0; 177 gbc.gridwidth = 1; 178 com.studentgui.uicomp.PhaseScoreField field = new com.studentgui.uicomp.PhaseScoreField(parts[i][1], 0); 179 field.setName("digitalliteracy_" + this.parts[i][0]); 180 field.getAccessibleContext().setAccessibleName(this.parts[i][1]); 181 field.setToolTipText("Enter whole number score for " + this.parts[i][1]); 182 gbc.gridx = 0; gbc.gridwidth = 2; gbc.insets = new Insets(5, 5, 5, 5); 183 dataEntryPanel.add(field, gbc); 184 skillFields[i] = field; 185 gbc.gridx = 2; gbc.gridwidth = 1; gbc.insets = new Insets(5, 0, 5, 5); 186 dataEntryPanel.add(new JPanel(), gbc); 187 } 188 189 gbc.gridy = this.parts.length + 3; 190 gbc.gridx = 0; 191 gbc.gridwidth = GridBagConstraints.REMAINDER; 192 gbc.weighty = 1.0; 193 dataEntryPanel.add(new JPanel(), gbc); 194 195 // Place Submit and Open Latest side-by-side and match IOS button height 196 gbc.gridy = this.parts.length + 4; 197 gbc.weighty = 0.0; 198 gbc.gridx = 0; 199 gbc.gridwidth = 1; 200 JButton submitDataButton = new JButton("Submit Data"); 201 submitDataButton.setPreferredSize(new java.awt.Dimension(0, 32)); 202 submitDataButton.addActionListener((ActionEvent e) -> { submitData(); refreshGraph(); }); 203 submitDataButton.setToolTipText("Save digital literacy scores for the selected student (Alt+S)"); 204 submitDataButton.setMnemonic(KeyEvent.VK_S); 205 submitDataButton.getAccessibleContext().setAccessibleName("Submit Digital Literacy Data"); 206 submitDataButton.setName("digitalliteracy_submit"); 207 dataEntryPanel.add(submitDataButton, gbc); 208 209 gbc.gridx = 1; 210 JButton openLatestBtn = new JButton("Open Latest Plot"); 211 openLatestBtn.setPreferredSize(new java.awt.Dimension(0, 32)); 212 openLatestBtn.addActionListener((ActionEvent e) -> { 213 java.nio.file.Path p = com.studentgui.apphelpers.Helpers.latestPlotPath(this.studentNameParam, "DigitalLiteracy"); 214 if (p == null) { 215 com.studentgui.apphelpers.UiNotifier.show("No DigitalLiteracy plot found for student"); 216 } else { 217 try { 218 java.awt.Desktop.getDesktop().open(p.toFile()); 219 } catch (java.io.IOException | UnsupportedOperationException | SecurityException ex) { 220 com.studentgui.apphelpers.UiNotifier.show("Unable to open plot: " + p.getFileName().toString()); 221 } 222 } 223 }); 224 dataEntryPanel.add(openLatestBtn, gbc); 225 226 gbc.gridx = 2; gbc.gridwidth = GridBagConstraints.REMAINDER; 227 dataEntryPanel.add(new JPanel(), gbc); 228 229 dataEntryScrollPane.getAccessibleContext().setAccessibleName("Digital Literacy data entry scroll pane"); 230 231 add(dataEntryScrollPane, BorderLayout.CENTER); 232 233 // Add existing graph reference 234 add(lineGraph, BorderLayout.SOUTH); 235 236 SwingUtilities.invokeLater(() -> { 237 dataEntryPanel.setPreferredSize(dataEntryPanel.getPreferredSize()); 238 updateTitleDate(); 239 revalidate(); 240 }); 241 242 // Ensure application folders and DB schema exist 243 com.studentgui.apphelpers.Helpers.createFolderHierarchy(); 244 initDatabase(); 245 refreshGraph(); 246 } 247 248 /** 249 * Ensure the progress type and assessment parts for DigitalLiteracy exist 250 * in the canonical schema. 251 */ 252 private void initDatabase() { 253 try { 254 int ptId = com.studentgui.apphelpers.Database.getOrCreateProgressType("DigitalLiteracy"); 255 // Use canonical part codes from this.parts 256 String[] codes = new String[this.parts.length]; 257 for (int i = 0; i < this.parts.length; i++) { 258 codes[i] = this.parts[i][0]; 259 } 260 com.studentgui.apphelpers.Database.ensureAssessmentParts(ptId, codes); 261 } catch (SQLException e) { 262 LOG.error("SQL error ensuring assessment parts for DigitalLiteracy", e); 263 } 264 } 265 266 /** 267 * Validate and persist input field values as a new progress session for 268 * the selected student. 269 */ 270 private void submitData() { 271 if (studentNameParam == null || studentNameParam.trim().isEmpty()) { 272 JOptionPane.showMessageDialog(this, "Please select a student before submitting Digital Literacy data.", "Missing student", JOptionPane.WARNING_MESSAGE); 273 return; 274 } 275 276 try { 277 int studentId = com.studentgui.apphelpers.Database.getOrCreateStudent(studentNameParam); 278 int ptId = com.studentgui.apphelpers.Database.getOrCreateProgressType("DigitalLiteracy"); 279 int sessionId = com.studentgui.apphelpers.Database.createProgressSession(studentId, ptId, dateParam); 280 281 String[] codes = new String[this.parts.length]; 282 int[] scores = new int[this.parts.length]; 283 for (int i = 0; i < this.parts.length; i++) { 284 codes[i] = this.parts[i][0]; 285 scores[i] = skillFields[i].getValue(); 286 } 287 com.studentgui.apphelpers.Database.insertAssessmentResults(sessionId, ptId, codes, scores); 288 LOG.info("Data submitted successfully via normalized schema."); 289 com.studentgui.apphelpers.UiNotifier.show("Digital Literacy data saved."); 290 com.studentgui.apphelpers.dto.AssessmentPayload payload = new com.studentgui.apphelpers.dto.AssessmentPayload(sessionId, codes, scores); 291 java.nio.file.Path jsonOut = com.studentgui.apphelpers.SessionJsonWriter.writeSessionJson(this.studentNameParam, "DigitalLiteracy", payload, sessionId); 292 if (jsonOut == null) { 293 LOG.warn("Unable to save DigitalLiteracy session JSON for sessionId={}", sessionId); 294 } 295 try { 296 java.nio.file.Path plotsOut = com.studentgui.apphelpers.Helpers.studentPlotsDir(this.studentNameParam); 297 java.nio.file.Path reportsOut = com.studentgui.apphelpers.Helpers.studentReportsDir(this.studentNameParam); 298 java.nio.file.Files.createDirectories(plotsOut); 299 java.nio.file.Files.createDirectories(reportsOut); 300 java.time.format.DateTimeFormatter df = java.time.format.DateTimeFormatter.ISO_DATE; 301 String dateStr = this.dateParam != null ? this.dateParam.format(df) : java.time.LocalDate.now().toString(); 302 String baseName = "DigitalLiteracy-" + sessionId + "-" + dateStr; 303 304 com.studentgui.apphelpers.Database.ResultsWithDates rwd = com.studentgui.apphelpers.Database.fetchLatestAssessmentResultsWithDates(this.studentNameParam, "DigitalLiteracy", Integer.MAX_VALUE); 305 java.util.Map<String, java.nio.file.Path> groups = null; 306 String[] labels = new String[this.parts.length]; 307 for (int i = 0; i < this.parts.length; i++) { 308 labels[i] = this.parts[i][1]; 309 } 310 if (rwd != null && rwd.rows != null && !rwd.rows.isEmpty()) { 311 lineGraph.updateWithGroupedDataByDate(rwd.dates, rwd.rows, codes, labels); 312 groups = lineGraph.saveGroupedCharts(plotsOut, baseName, 1000, 240); 313 java.time.LocalDate headerDate = rwd.dates.get(rwd.dates.size() - 1); 314 dateStr = headerDate.format(df); 315 } else { 316 java.util.List<java.util.List<Integer>> rowsList = new java.util.ArrayList<>(); 317 java.util.List<Integer> latest = new java.util.ArrayList<>(); 318 for (int v : scores) latest.add(v); 319 rowsList.add(latest); 320 lineGraph.updateWithGroupedData(rowsList, codes); 321 groups = lineGraph.saveGroupedCharts(plotsOut, baseName, 1000, 240); 322 } 323 324 if (groups == null) { 325 groups = new java.util.LinkedHashMap<>(); 326 } 327 StringBuilder md = new StringBuilder(); 328 md.append("# ").append(this.studentNameParam == null ? "Unknown Student" : this.studentNameParam).append(" - ").append(dateStr).append("\n\n"); 329 for (java.util.Map.Entry<String, java.nio.file.Path> e : groups.entrySet()) { 330 md.append("## ").append(e.getKey()).append("\n\n"); 331 md.append(".append(e.getValue().getFileName().toString()).append(")\n\n"); 332 } 333 java.nio.file.Path mdFile = reportsOut.resolve(baseName + ".md"); 334 java.nio.file.Files.writeString(mdFile, md.toString(), java.nio.charset.StandardCharsets.UTF_8); 335 336 try { 337 String[] palette = JLineGraph.PALETTE_HEX; 338 java.util.LinkedHashMap<String, java.util.List<Integer>> groupsIdx = new java.util.LinkedHashMap<>(); 339 for (int i = 0; i < codes.length; i++) { 340 String code = codes[i]; 341 String grp = code != null && code.contains("_") ? code.split("_")[0] : code; 342 groupsIdx.computeIfAbsent(grp, k -> new java.util.ArrayList<>()).add(i); 343 } 344 StringBuilder html = new StringBuilder(); 345 html.append("<!doctype html><html><head><meta charset=\"utf-8\"><title>"); 346 html.append(this.studentNameParam == null ? "Student Report" : this.studentNameParam).append(" - ").append(dateStr).append("</title>"); 347 html.append("<style>body{font-family:sans-serif;margin:20px;} img{max-width:100%;height:auto;border:1px solid #ccc;margin-bottom:8px;} .legend{max-height:160px;overflow:auto;border:1px solid #ddd;padding:8px;margin-bottom:24px;} .legend-item{display:flex;align-items:center;gap:8px;padding:4px 0;} .swatch{width:18px;height:12px;border:1px solid #333;display:inline-block}</style>"); 348 html.append("</head><body>"); 349 html.append("<h1>").append(this.studentNameParam == null ? "Unknown Student" : this.studentNameParam).append(" - ").append(dateStr).append("</h1>"); 350 for (java.util.Map.Entry<String, java.nio.file.Path> e2 : groups.entrySet()) { 351 String grp = e2.getKey(); 352 String imgName = e2.getValue().getFileName().toString(); 353 html.append("<h2>").append(grp).append("</h2>"); 354 html.append("<div class=\"plot\"><img src=\"../plots/").append(imgName).append("\" alt=\"").append(grp).append("\"></div>"); 355 java.util.List<Integer> idxs = groupsIdx.getOrDefault(grp, new java.util.ArrayList<>()); 356 html.append("<div class=\"legend\">"); 357 for (int s = 0; s < idxs.size(); s++) { 358 int idx = idxs.get(s); 359 String code = codes[idx]; 360 String human = this.parts[idx][1]; 361 String seriesName = code + " - " + human; 362 String color = palette[s % palette.length]; 363 html.append("<div class=\"legend-item\">"); 364 html.append("<span class=\"swatch\" style=\"background:"); 365 html.append(color); 366 html.append(";\"></span>"); 367 html.append("<div>"); 368 html.append(seriesName); 369 html.append("</div></div>"); 370 } 371 html.append("</div>"); 372 } 373 html.append("</body></html>"); 374 java.nio.file.Path htmlFile = reportsOut.resolve(baseName + ".html"); 375 java.nio.file.Files.writeString(htmlFile, html.toString(), java.nio.charset.StandardCharsets.UTF_8); 376 LOG.info("Wrote DigitalLiteracy HTML session report {}", htmlFile); 377 } catch (java.io.IOException ioex) { 378 LOG.warn("Unable to write DigitalLiteracy HTML report: {}", ioex.toString()); 379 } 380 } catch (java.io.IOException ioe) { 381 LOG.warn("Unable to save DigitalLiteracy per-phase plots or markdown report: {}", ioe.toString()); 382 } 383 } catch (SQLException e) { 384 LOG.error("SQL error submitting Digital Literacy data", e); 385 JOptionPane.showMessageDialog(this, "Database error saving Digital Literacy data: " + e.getMessage(), "Database error", JOptionPane.ERROR_MESSAGE); 386 } 387 } 388 389 /** 390 * Load recent assessment sessions and update the shared {@link JLineGraph} 391 * component with the returned values. 392 */ 393 private void refreshGraph() { 394 try { 395 List<List<Integer>> allSkillValues = com.studentgui.apphelpers.Database.fetchLatestAssessmentResults(studentNameParam, "DigitalLiteracy", 5); 396 if (allSkillValues != null && !allSkillValues.isEmpty()) { 397 // Build canonical codes array in the same order used when ensuring parts 398 String[] codes = new String[this.parts.length]; 399 for (int i = 0; i < this.parts.length; i++) { 400 codes[i] = this.parts[i][0]; 401 } 402 lineGraph.updateWithGroupedData(allSkillValues, codes); 403 // Write to the consolidated per-run data dumps file when enabled 404 if (Boolean.parseBoolean(com.studentgui.apphelpers.Settings.get("dump.enabled", "false"))) { 405 try { 406 String appHome = System.getProperty("APP_HOME", com.studentgui.apphelpers.Helpers.APP_HOME.toString()); 407 String ts = System.getProperty("LOG_TS", String.valueOf(java.time.Instant.now().getEpochSecond())); 408 java.nio.file.Path logDir = java.nio.file.Paths.get(appHome).resolve("logs"); 409 java.nio.file.Files.createDirectories(logDir); 410 java.nio.file.Path logFile = logDir.resolve("data_dumps_" + ts + ".log"); 411 StringBuilder sb = new StringBuilder(); 412 sb.append("[DigitalLiteracy]").append(System.lineSeparator()); 413 sb.append(java.time.Instant.now().toString()).append(" - student=").append(this.studentNameParam).append(System.lineSeparator()); 414 sb.append("data=").append(allSkillValues.toString()).append(System.lineSeparator()); 415 sb.append(System.lineSeparator()); 416 java.nio.file.Files.writeString(logFile, sb.toString(), java.nio.charset.StandardCharsets.UTF_8, java.nio.file.StandardOpenOption.CREATE, java.nio.file.StandardOpenOption.APPEND); 417 } catch (java.io.IOException ioe) { 418 LOG.trace("Unable to write DigitalLiteracy load log: {}", ioe.toString()); 419 } 420 } 421 } else { 422 LOG.info("No data to plot."); 423 } 424 } catch (SQLException e) { 425 LOG.error("SQL error refreshing Digital Literacy graph", e); 426 } 427 } 428 429 @Override 430 /** 431 * Update the displayed date for the Digital Literacy page and refresh UI. 432 * 433 * Records `dateParam` and schedules a UI refresh on the Swing EDT so the 434 * chart and title display the selected date. 435 * 436 * @param newDate the date to display (may be null to use current date) 437 */ 438 439 public void dateChanged(final LocalDate newDate) { 440 this.dateParam = newDate; 441 SwingUtilities.invokeLater(() -> { 442 refreshGraph(); 443 updateTitleDate(); 444 }); 445 } 446 447 @Override 448 /** 449 * Update the selected student for the Digital Literacy page and refresh UI. 450 * 451 * Sets `studentNameParam` and posts a refresh on the Swing EDT to reload 452 * data and update the page title. 453 * 454 * @param newStudent student identifier (name or id) to display; may be null 455 */ 456 457 public void studentChanged(final String newStudent) { 458 this.studentNameParam = newStudent; 459 SwingUtilities.invokeLater(() -> { 460 refreshGraph(); 461 updateTitleDate(); 462 }); 463 } 464 465 private void updateTitleDate() { 466 try { 467 String dateStr = this.dateParam != null ? this.dateParam.toString() : java.time.LocalDate.now().toString(); 468 this.titleLabel.setText(baseTitle + " - " + dateStr); 469 } catch (Exception ex) { 470 this.titleLabel.setText(baseTitle); 471 } 472 } 473 474 475}