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("![](../plots/").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}